feat: passport images for user

This commit is contained in:
2024-12-04 20:21:01 +04:00
parent 1795cacc5b
commit 2cb62a4e0b
15 changed files with 260 additions and 60 deletions

View File

@@ -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';

View 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;
};

View 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;
};

View 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);
};

View File

@@ -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);
};

View File

@@ -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);

View File

@@ -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);
};

View File

@@ -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`,
},
});
}
}

View File

@@ -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"

View 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;

View File

@@ -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;

View File

@@ -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={"Роль и должность"}>

View File

@@ -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;

View File

@@ -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",

View 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;