mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
feat(FE-170,174,175): enhance RecordingForm with product warehouse integration and improve data handling
This commit is contained in:
@@ -235,9 +235,11 @@ export type RecordingGradingFormValues = Yup.InferType<
|
|||||||
|
|
||||||
type RecordingFormData = Partial<Recording> & {
|
type RecordingFormData = Partial<Recording> & {
|
||||||
body_weights?: CreateGrowingRecordingPayload['body_weights'];
|
body_weights?: CreateGrowingRecordingPayload['body_weights'];
|
||||||
stocks?: CreateGrowingRecordingPayload['stocks'];
|
stocks?: CreateGrowingRecordingPayload['stocks'] | Recording['stocks'];
|
||||||
depletions?: CreateGrowingRecordingPayload['depletions'];
|
depletions?: CreateGrowingRecordingPayload['depletions'] | Recording['depletions'];
|
||||||
eggs?: CreateLayingRecordingPayload['eggs'];
|
eggs?: CreateLayingRecordingPayload['eggs'] | Recording['eggs'];
|
||||||
|
project_flock_kandang_id?: number;
|
||||||
|
project_flock_category?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRecordingGrowingFormInitialValues = (
|
export const getRecordingGrowingFormInitialValues = (
|
||||||
@@ -265,17 +267,15 @@ export const getRecordingGrowingFormInitialValues = (
|
|||||||
total_weight: 0,
|
total_weight: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
stocks: initialValues?.stocks?.map(
|
stocks: initialValues?.stocks?.map((stock) => ({
|
||||||
(stock: NonNullable<CreateGrowingRecordingPayload['stocks']>[0]) => ({
|
|
||||||
product_warehouse_id: stock.product_warehouse_id,
|
product_warehouse_id: stock.product_warehouse_id,
|
||||||
qty: stock.qty,
|
qty: (stock as { qty?: number; usage_amount?: number }).qty || (stock as { qty?: number; usage_amount?: number }).usage_amount || '',
|
||||||
})
|
})) ?? [
|
||||||
) ?? [
|
{
|
||||||
{
|
product_warehouse_id: 0,
|
||||||
product_warehouse_id: 0,
|
qty: '',
|
||||||
qty: '',
|
},
|
||||||
},
|
],
|
||||||
],
|
|
||||||
depletions: initialValues?.depletions?.map(
|
depletions: initialValues?.depletions?.map(
|
||||||
(
|
(
|
||||||
depletion: NonNullable<CreateGrowingRecordingPayload['depletions']>[0]
|
depletion: NonNullable<CreateGrowingRecordingPayload['depletions']>[0]
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import { ProjectFlockApi } from '@/services/api/production';
|
|||||||
import { LocationApi } from '@/services/api/master-data';
|
import { LocationApi } from '@/services/api/master-data';
|
||||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||||
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn, formatDate } from '@/lib/helper';
|
||||||
import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock';
|
import { ProjectFlockKandangLookup } from '@/types/api/production/project-flock';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
@@ -197,7 +197,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
const existingRecordingsUrl = useMemo(() => {
|
const existingRecordingsUrl = useMemo(() => {
|
||||||
return `${RecordingApi.basePath}?record_date=${today}`;
|
return `${RecordingApi.basePath}?record_date=${today}`;
|
||||||
}, []);
|
}, [today]);
|
||||||
|
|
||||||
const { data: existingRecordings } = useSWR(
|
const { data: existingRecordings } = useSWR(
|
||||||
existingRecordingsUrl,
|
existingRecordingsUrl,
|
||||||
@@ -310,30 +310,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
initialValues.stocks &&
|
initialValues.stocks &&
|
||||||
type !== 'add'
|
type !== 'add'
|
||||||
) {
|
) {
|
||||||
const initialValuesWithStocks = initialValues as Recording & {
|
initialValues.stocks?.forEach((stock) => {
|
||||||
stocks?: Array<{
|
if (stock.product_warehouse && stock.product_warehouse.product) {
|
||||||
product_warehouse_id: number;
|
|
||||||
usage_amount: number;
|
|
||||||
notes: string;
|
|
||||||
product_warehouse?: {
|
|
||||||
id: number;
|
|
||||||
product_id: number;
|
|
||||||
product_name: string;
|
|
||||||
warehouse_id: number;
|
|
||||||
warehouse_name: string;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
||||||
initialValuesWithStocks.stocks?.forEach((stock) => {
|
|
||||||
if (stock.product_warehouse && stock.product_warehouse.product_name) {
|
|
||||||
const existingOption = options.find(
|
const existingOption = options.find(
|
||||||
(opt) => opt.value === stock.product_warehouse_id
|
(opt) => opt.value === stock.product_warehouse_id
|
||||||
);
|
);
|
||||||
if (!existingOption) {
|
if (!existingOption) {
|
||||||
options.push({
|
options.push({
|
||||||
value: stock.product_warehouse_id,
|
value: stock.product_warehouse_id,
|
||||||
label: `${stock.product_warehouse.product_name} - ${stock.product_warehouse.warehouse_name}`,
|
label: `${stock.product_warehouse.product.name} - ${stock.product_warehouse.product?.sku || ''}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -362,8 +347,27 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (initialValues && initialValues.depletions && type !== 'add') {
|
||||||
|
initialValues.depletions.forEach((depletion) => {
|
||||||
|
if (
|
||||||
|
depletion.product_warehouse &&
|
||||||
|
depletion.product_warehouse.product
|
||||||
|
) {
|
||||||
|
const existingOption = options.find(
|
||||||
|
(opt) => opt.value === depletion.product_warehouse_id
|
||||||
|
);
|
||||||
|
if (!existingOption) {
|
||||||
|
options.push({
|
||||||
|
value: depletion.product_warehouse_id,
|
||||||
|
label: depletion.product_warehouse.product.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}, [depletionProductsData]);
|
}, [depletionProductsData, initialValues, type]);
|
||||||
|
|
||||||
const eggProducts = useMemo(() => {
|
const eggProducts = useMemo(() => {
|
||||||
const options: OptionType[] = [];
|
const options: OptionType[] = [];
|
||||||
@@ -383,10 +387,27 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (initialValues && initialValues.eggs && type !== 'add') {
|
||||||
|
initialValues.eggs.forEach((egg) => {
|
||||||
|
if (egg.product_warehouse && egg.product_warehouse.product) {
|
||||||
|
const existingOption = options.find(
|
||||||
|
(opt) => opt.value === egg.product_warehouse_id
|
||||||
|
);
|
||||||
|
if (!existingOption) {
|
||||||
|
options.push({
|
||||||
|
value: egg.product_warehouse_id,
|
||||||
|
label: egg.product_warehouse.product.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}, [eggProductsData]);
|
}, [eggProductsData, initialValues, type]);
|
||||||
|
|
||||||
const isLayingCategory =
|
const isLayingCategory =
|
||||||
|
initialValues?.project_flock_category === 'LAYING' ||
|
||||||
projectFlockKandangLookup?.project_flock?.category === 'LAYING';
|
projectFlockKandangLookup?.project_flock?.category === 'LAYING';
|
||||||
|
|
||||||
const formikInitialValues = useMemo(() => {
|
const formikInitialValues = useMemo(() => {
|
||||||
@@ -824,7 +845,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
formik.setFieldValue(`body_weights.${idx}.avg_weight`, 0);
|
formik.setFieldValue(`body_weights.${idx}.avg_weight`, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update total_weight
|
|
||||||
formik.setFieldValue(`body_weights.${idx}.total_weight`, totalWeight);
|
formik.setFieldValue(`body_weights.${idx}.total_weight`, totalWeight);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -844,7 +864,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
if (qty > 0 && value > 0) {
|
if (qty > 0 && value > 0) {
|
||||||
const totalWeight = value * qty;
|
const totalWeight = value * qty;
|
||||||
formik.setFieldValue(`body_weights.${idx}.weight`, totalWeight);
|
formik.setFieldValue(`body_weights.${idx}.weight`, totalWeight);
|
||||||
// Update total_weight
|
|
||||||
formik.setFieldValue(`body_weights.${idx}.total_weight`, totalWeight);
|
formik.setFieldValue(`body_weights.${idx}.total_weight`, totalWeight);
|
||||||
} else {
|
} else {
|
||||||
formik.setFieldValue(`body_weights.${idx}.weight`, 0);
|
formik.setFieldValue(`body_weights.${idx}.weight`, 0);
|
||||||
@@ -874,7 +893,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
formik.setFieldValue(`body_weights.${idx}.avg_weight`, 0);
|
formik.setFieldValue(`body_weights.${idx}.avg_weight`, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update total_weight
|
|
||||||
formik.setFieldValue(`body_weights.${idx}.total_weight`, totalWeight);
|
formik.setFieldValue(`body_weights.${idx}.total_weight`, totalWeight);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1045,6 +1063,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
}
|
}
|
||||||
}, [isLayingCategory, type]);
|
}, [isLayingCategory, type]);
|
||||||
|
|
||||||
|
const bodyWeightValues = useMemo(() => {
|
||||||
|
if (!formik.values.body_weights) return [];
|
||||||
|
return formik.values.body_weights.map((w) => ({
|
||||||
|
weight: w.weight,
|
||||||
|
qty: w.qty,
|
||||||
|
}));
|
||||||
|
}, [formik.values.body_weights]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formik.values.body_weights && editingAverageIndex === null) {
|
if (formik.values.body_weights && editingAverageIndex === null) {
|
||||||
const updatedBodyWeights = formik.values.body_weights.map(
|
const updatedBodyWeights = formik.values.body_weights.map(
|
||||||
@@ -1074,12 +1100,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
formik.setFieldValue('body_weights', updatedBodyWeights, false);
|
formik.setFieldValue('body_weights', updatedBodyWeights, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [bodyWeightValues, editingAverageIndex, manuallyEditedRows]);
|
||||||
formik.values.body_weights?.map((w) => w.weight),
|
|
||||||
formik.values.body_weights?.map((w) => w.qty),
|
|
||||||
editingAverageIndex,
|
|
||||||
manuallyEditedRows,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -1172,73 +1193,124 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
className='w-full mt-8 flex flex-col gap-6'
|
className='w-full mt-8 flex flex-col gap-6'
|
||||||
>
|
>
|
||||||
{/* Basic Info Card */}
|
{/* Basic Info Card */}
|
||||||
<Card
|
{(type === 'add' || type === 'edit') && (
|
||||||
title='Informasi Recording'
|
<Card
|
||||||
className={{
|
title='Informasi Recording'
|
||||||
wrapper: 'w-full mb-4 shadow',
|
className={{
|
||||||
body: 'flex flex-col gap-6',
|
wrapper: 'w-full mb-4 shadow',
|
||||||
}}
|
body: 'flex flex-col gap-6',
|
||||||
>
|
}}
|
||||||
<div className={'grid grid-cols-3 gap-4'}>
|
>
|
||||||
<>
|
<div className={'grid grid-cols-3 gap-4'}>
|
||||||
<SelectInput
|
<>
|
||||||
key={`location-select-${selectedLocation?.value || 'default'}`}
|
<SelectInput
|
||||||
required
|
key={`location-select-${selectedLocation?.value || 'default'}`}
|
||||||
label='Lokasi'
|
required
|
||||||
value={selectedLocation}
|
label='Lokasi'
|
||||||
onChange={locationChangeHandler}
|
value={selectedLocation}
|
||||||
options={locationOptions}
|
onChange={locationChangeHandler}
|
||||||
onInputChange={setLocationSearchValue}
|
options={locationOptions}
|
||||||
isLoading={isLoadingLocations}
|
onInputChange={setLocationSearchValue}
|
||||||
placeholder='Pilih Lokasi'
|
isLoading={isLoadingLocations}
|
||||||
isClearable
|
placeholder='Pilih Lokasi'
|
||||||
isSearchable
|
isClearable
|
||||||
/>
|
isSearchable
|
||||||
|
/>
|
||||||
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
key={`project-flock-select-${selectedProjectFlock?.value || 'default'}`}
|
key={`project-flock-select-${selectedProjectFlock?.value || 'default'}`}
|
||||||
required
|
required
|
||||||
label='Project Flock'
|
label='Project Flock'
|
||||||
value={selectedProjectFlock}
|
value={selectedProjectFlock}
|
||||||
onChange={projectFlockChangeHandler}
|
onChange={projectFlockChangeHandler}
|
||||||
options={projectFlockOptions}
|
options={projectFlockOptions}
|
||||||
onInputChange={setProjectFlockSearchValue}
|
onInputChange={setProjectFlockSearchValue}
|
||||||
isLoading={isLoadingProjectFlocks}
|
isLoading={isLoadingProjectFlocks}
|
||||||
isDisabled={!selectedLocation}
|
isDisabled={!selectedLocation}
|
||||||
placeholder={
|
placeholder={
|
||||||
selectedLocation
|
selectedLocation
|
||||||
? 'Pilih Project Flock'
|
? 'Pilih Project Flock'
|
||||||
: 'Pilih Lokasi terlebih dahulu'
|
: 'Pilih Lokasi terlebih dahulu'
|
||||||
}
|
}
|
||||||
isClearable
|
isClearable
|
||||||
isSearchable
|
isSearchable
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
key={`kandang-select-${projectFlockKandangLookup?.project_flock_kandang_id || 'default'}`}
|
key={`kandang-select-${projectFlockKandangLookup?.project_flock_kandang_id || 'default'}`}
|
||||||
required
|
required
|
||||||
label='Kandang'
|
label='Kandang'
|
||||||
value={selectedKandang}
|
value={selectedKandang}
|
||||||
onChange={kandangChangeHandler}
|
onChange={kandangChangeHandler}
|
||||||
options={kandangOptions}
|
options={kandangOptions}
|
||||||
isLoading={false}
|
isLoading={false}
|
||||||
isDisabled={!selectedProjectFlock}
|
isDisabled={!selectedProjectFlock}
|
||||||
placeholder={
|
placeholder={
|
||||||
selectedProjectFlock
|
selectedProjectFlock
|
||||||
? 'Pilih Kandang'
|
? 'Pilih Kandang'
|
||||||
: 'Pilih Project Flock terlebih dahulu'
|
: 'Pilih Project Flock terlebih dahulu'
|
||||||
}
|
}
|
||||||
isClearable
|
isClearable
|
||||||
isSearchable={false}
|
isSearchable={false}
|
||||||
startAdornment={
|
startAdornment={
|
||||||
projectFlockKandangLookup
|
projectFlockKandangLookup
|
||||||
? getProjectFlockBadgeAdornment()
|
? getProjectFlockBadgeAdornment()
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Recording Info for Detail View */}
|
||||||
|
{type === 'detail' && initialValues && (
|
||||||
|
<Card
|
||||||
|
title='Informasi Recording'
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full mb-4 shadow',
|
||||||
|
body: 'flex flex-col gap-4',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='grid grid-cols-2 md:grid-cols-4 gap-4'>
|
||||||
|
<div>
|
||||||
|
<span className='text-sm text-gray-600'>Recording ID</span>
|
||||||
|
<p className='font-semibold'>#{initialValues.id}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className='text-sm text-gray-600'>
|
||||||
|
Tanggal Recording
|
||||||
|
</span>
|
||||||
|
<p className='font-semibold'>
|
||||||
|
{formatDate(
|
||||||
|
initialValues.record_datetime || '',
|
||||||
|
'DD MMMM YYYY'
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className='text-sm text-gray-600'>Hari</span>
|
||||||
|
<p className='font-semibold'>Hari ke-{initialValues.day}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className='text-sm text-gray-600'>Kategori</span>
|
||||||
|
<p className='font-semibold'>
|
||||||
|
<Badge
|
||||||
|
variant='soft'
|
||||||
|
color={
|
||||||
|
initialValues.project_flock_category === 'LAYING'
|
||||||
|
? 'info'
|
||||||
|
: 'warning'
|
||||||
|
}
|
||||||
|
size='sm'
|
||||||
|
>
|
||||||
|
{initialValues.project_flock_category}
|
||||||
|
</Badge>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Body Weights Table */}
|
{/* Body Weights Table */}
|
||||||
<Card
|
<Card
|
||||||
@@ -2014,7 +2086,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
option?.value || 0
|
option?.value || 0
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
options={depletionProducts}
|
options={eggProducts}
|
||||||
placeholder='Pilih Kondisi Telur'
|
placeholder='Pilih Kondisi Telur'
|
||||||
isLoading={isLoadingEggProducts}
|
isLoading={isLoadingEggProducts}
|
||||||
isError={
|
isError={
|
||||||
@@ -2228,7 +2300,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
hasExceededStock || !formik.isValid || formik.isSubmitting
|
hasExceededStock || !formik.isValid || formik.isSubmitting
|
||||||
}
|
}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const result = await formik.submitForm();
|
await formik.submitForm();
|
||||||
if (
|
if (
|
||||||
formik.isValid &&
|
formik.isValid &&
|
||||||
!formik.isSubmitting &&
|
!formik.isSubmitting &&
|
||||||
|
|||||||
+10
@@ -1,4 +1,5 @@
|
|||||||
import { BaseApproval, BaseMetadata, User } from '@/types/api/api-general';
|
import { BaseApproval, BaseMetadata, User } from '@/types/api/api-general';
|
||||||
|
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
||||||
|
|
||||||
export type ProductionMetrics = {
|
export type ProductionMetrics = {
|
||||||
total_depletion_qty: number;
|
total_depletion_qty: number;
|
||||||
@@ -33,14 +34,18 @@ export type RecordingDepletion = {
|
|||||||
recording_id: number;
|
recording_id: number;
|
||||||
product_warehouse_id: number;
|
product_warehouse_id: number;
|
||||||
qty: number;
|
qty: number;
|
||||||
|
product_warehouse: ProductWarehouse;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RecordingStock = {
|
export type RecordingStock = {
|
||||||
id: number;
|
id: number;
|
||||||
recording_id: number;
|
recording_id: number;
|
||||||
product_warehouse_id: number;
|
product_warehouse_id: number;
|
||||||
|
usage_amount?: number;
|
||||||
usage_qty: number;
|
usage_qty: number;
|
||||||
|
qty: number;
|
||||||
pending_qty: number;
|
pending_qty: number;
|
||||||
|
product_warehouse: ProductWarehouse;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RecordingEgg = {
|
export type RecordingEgg = {
|
||||||
@@ -49,6 +54,7 @@ export type RecordingEgg = {
|
|||||||
product_warehouse_id: number;
|
product_warehouse_id: number;
|
||||||
qty: number;
|
qty: number;
|
||||||
created_by: User;
|
created_by: User;
|
||||||
|
product_warehouse: ProductWarehouse;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GradingEgg = {
|
export type GradingEgg = {
|
||||||
@@ -66,6 +72,10 @@ export type Recording = BaseMetadata &
|
|||||||
egg_grading_status?: string | null;
|
egg_grading_status?: string | null;
|
||||||
egg_grading_pending_qty?: number | null;
|
egg_grading_pending_qty?: number | null;
|
||||||
egg_grading_completed_qty?: number | null;
|
egg_grading_completed_qty?: number | null;
|
||||||
|
body_weights?: RecordingBW[];
|
||||||
|
depletions?: RecordingDepletion[];
|
||||||
|
stocks?: RecordingStock[];
|
||||||
|
eggs?: RecordingEgg[];
|
||||||
recording_bws?: RecordingBW[];
|
recording_bws?: RecordingBW[];
|
||||||
recording_depletions?: RecordingDepletion[];
|
recording_depletions?: RecordingDepletion[];
|
||||||
recording_stocks?: RecordingStock[];
|
recording_stocks?: RecordingStock[];
|
||||||
|
|||||||
Reference in New Issue
Block a user