Merged PR 434: 画面実装(テンプレートファイルアップロードPopupデザイン)
## 概要 [Task2664: 画面実装(テンプレートファイルアップロードPopupデザイン)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2664) - デザイン反映 - ファイルピッカーからファイル取得→storeに保存 ## レビューポイント - デザイン反映に不備はあるか - 想定としてstoreに保持したfileを、Operationsでblobにアップロードする流れにしようとしているがよさそうか ## UIの変更 - https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task2664?csf=1&web=1&e=optFai ## 動作確認状況 - ローカルで確認、develop環境で確認など ## 補足 - 相談、参考資料などがあれば
This commit is contained in:
parent
ecc44e58e0
commit
9ca4ae61f8
@ -0,0 +1,2 @@
|
||||
// アップロード可能なファイルサイズの上限(MB)
|
||||
export const UPLOAD_FILE_SIZE_LIMIT: number = 5 * 1024 * 1024;
|
||||
@ -1,7 +1,26 @@
|
||||
import { RootState } from "app/store";
|
||||
import { UPLOAD_FILE_SIZE_LIMIT } from "./constants";
|
||||
|
||||
export const selectTemplates = (state: RootState) =>
|
||||
state.template.domain.templates;
|
||||
|
||||
export const selectIsLoading = (state: RootState) =>
|
||||
state.template.apps.isLoading;
|
||||
|
||||
export const selectUploadFile = (state: RootState) =>
|
||||
state.template.apps.uploadFile;
|
||||
|
||||
export const selectUploadFileError = (state: RootState) => {
|
||||
const { uploadFile } = state.template.apps;
|
||||
|
||||
// 必須チェック
|
||||
if (!uploadFile) {
|
||||
return { hasErrorRequired: true, hasErrorFileSize: false };
|
||||
}
|
||||
// ファイルサイズチェック(5MB)
|
||||
if (uploadFile.size > UPLOAD_FILE_SIZE_LIMIT) {
|
||||
return { hasErrorRequired: false, hasErrorFileSize: true };
|
||||
}
|
||||
|
||||
return { hasErrorRequired: false, hasErrorFileSize: false };
|
||||
};
|
||||
|
||||
@ -7,6 +7,7 @@ export interface TemplateState {
|
||||
|
||||
export interface Apps {
|
||||
isLoading: boolean;
|
||||
uploadFile?: File;
|
||||
}
|
||||
|
||||
export interface Domain {
|
||||
|
||||
@ -1,20 +1,27 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
import { TemplateState } from "./state";
|
||||
import { listTemplateAsync } from "./operations";
|
||||
|
||||
const initialState: TemplateState = {
|
||||
apps: {
|
||||
isLoading: false,
|
||||
uploadFile: undefined,
|
||||
},
|
||||
domain: {
|
||||
templates: undefined,
|
||||
},
|
||||
domain: {},
|
||||
};
|
||||
|
||||
export const templateSlice = createSlice({
|
||||
name: "template",
|
||||
initialState,
|
||||
reducers: {},
|
||||
reducers: {
|
||||
cleanupTemplate: (state) => {
|
||||
state.apps.uploadFile = initialState.apps.uploadFile;
|
||||
},
|
||||
changeUploadFile: (state, action: PayloadAction<{ file: File }>) => {
|
||||
const { file } = action.payload;
|
||||
state.apps.uploadFile = file;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(listTemplateAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
@ -31,4 +38,6 @@ export const templateSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
export const { changeUploadFile, cleanupTemplate } = templateSlice.actions;
|
||||
|
||||
export default templateSlice.reducer;
|
||||
|
||||
@ -127,7 +127,8 @@ const AccountPage: React.FC = (): JSX.Element => {
|
||||
{isTier5 && !viewInfo.account.parentAccountName && (
|
||||
<dd className={styles.form}>
|
||||
<select
|
||||
className={`${styles.formInput} ${styles.required}`}
|
||||
className={styles.formInput}
|
||||
required
|
||||
onChange={(event) => {
|
||||
dispatch(
|
||||
changeDealer({
|
||||
@ -210,7 +211,8 @@ const AccountPage: React.FC = (): JSX.Element => {
|
||||
<dd className={styles.form}>
|
||||
<select
|
||||
name=""
|
||||
className={`${styles.formInput} ${styles.required}`}
|
||||
className={styles.formInput}
|
||||
required
|
||||
onChange={(event) => {
|
||||
dispatch(
|
||||
changePrimaryAdministrator({
|
||||
@ -269,7 +271,8 @@ const AccountPage: React.FC = (): JSX.Element => {
|
||||
<dd className={styles.form}>
|
||||
<select
|
||||
name=""
|
||||
className={`${styles.formInput} ${styles.required}`}
|
||||
className={styles.formInput}
|
||||
required
|
||||
onChange={(event) => {
|
||||
dispatch(
|
||||
changeSecondryAdministrator({
|
||||
|
||||
@ -0,0 +1,99 @@
|
||||
import React, { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getTranslationID } from "translation";
|
||||
import styles from "styles/app.module.scss";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { AppDispatch } from "app/store";
|
||||
import {
|
||||
cleanupTemplate,
|
||||
selectUploadFile,
|
||||
changeUploadFile,
|
||||
} from "features/workflow/template";
|
||||
import close from "../../assets/images/close.svg";
|
||||
|
||||
interface AddTemplateFilePopupProps {
|
||||
onClose: () => void;
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export const AddTemplateFilePopup: React.FC<AddTemplateFilePopupProps> = (
|
||||
props: AddTemplateFilePopupProps
|
||||
) => {
|
||||
const { onClose, isOpen } = props;
|
||||
const [t] = useTranslation();
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
|
||||
// 閉じるボタンを押したときの処理
|
||||
const closePopup = useCallback(() => {
|
||||
onClose();
|
||||
dispatch(cleanupTemplate());
|
||||
}, [onClose, dispatch]);
|
||||
|
||||
// アップロード対象のファイル情報
|
||||
const uploadFile = useSelector(selectUploadFile);
|
||||
|
||||
// ファイルが選択されたときの処理
|
||||
const handleFileChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
// 選択されたファイルを取得(複数選択されても先頭を取得)
|
||||
const file = event.target.files?.[0];
|
||||
|
||||
// ファイルが選択されていれば、storeに保存
|
||||
if (file) {
|
||||
dispatch(changeUploadFile({ file }));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`${styles.modal} ${isOpen ? styles.isShow : ""}`}>
|
||||
<div className={styles.modalBox}>
|
||||
<p className={styles.modalTitle}>
|
||||
{t(getTranslationID("templateFilePage.label.addTemplate"))}
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions */}
|
||||
<img
|
||||
src={close}
|
||||
className={styles.modalTitleIcon}
|
||||
alt="close"
|
||||
onClick={closePopup}
|
||||
/>
|
||||
</p>
|
||||
<form className={styles.form}>
|
||||
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||
<dt className={styles.formTitle} />
|
||||
<dd
|
||||
className={`${styles.full} ${styles.alignCenter} ${styles.last}`}
|
||||
>
|
||||
<p className={styles.formFileName} style={{ marginRight: "5px" }}>
|
||||
{uploadFile?.name ??
|
||||
t(getTranslationID("templateFilePage.label.notFileChosen"))}
|
||||
</p>
|
||||
|
||||
<label htmlFor="template" className={styles.formFileButton}>
|
||||
{t(getTranslationID("templateFilePage.label.chooseFile"))}
|
||||
<input
|
||||
type="file"
|
||||
id="template"
|
||||
className={`${styles.formInput} ${styles.isHide}`}
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
</label>
|
||||
</dd>
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<input
|
||||
type="button"
|
||||
name="submit"
|
||||
value={t(
|
||||
getTranslationID("templateFilePage.label.addTemplate")
|
||||
)}
|
||||
className={`${styles.formSubmit} ${styles.marginBtm1} ${styles.isActive}`}
|
||||
/>
|
||||
</dd>
|
||||
</dl>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { AppDispatch } from "app/store";
|
||||
import Header from "components/header";
|
||||
@ -7,13 +7,14 @@ import { useTranslation } from "react-i18next";
|
||||
import { getTranslationID } from "translation";
|
||||
import undo from "assets/images/undo.svg";
|
||||
import styles from "styles/app.module.scss";
|
||||
import {
|
||||
listTemplateAsync,
|
||||
selectIsLoading,
|
||||
selectTemplates,
|
||||
} from "features/workflow/template";
|
||||
import addTemplate from "assets/images/template_add.svg";
|
||||
import progress_activit from "assets/images/progress_activit.svg";
|
||||
import {
|
||||
selectTemplates,
|
||||
listTemplateAsync,
|
||||
selectIsLoading,
|
||||
} from "features/workflow/template";
|
||||
import { AddTemplateFilePopup } from "./addTemplateFilePopup";
|
||||
|
||||
export const TemplateFilePage: React.FC = () => {
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
@ -21,89 +22,106 @@ export const TemplateFilePage: React.FC = () => {
|
||||
const templates = useSelector(selectTemplates);
|
||||
const isLoading = useSelector(selectIsLoading);
|
||||
|
||||
// 追加Popupの表示制御
|
||||
const [isShowAddPopup, setIsShowAddPopup] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(listTemplateAsync());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<Header userName="XXXXXXXX" />
|
||||
<UpdateTokenTimer />
|
||||
<main className={styles.main}>
|
||||
<div>
|
||||
<div className={styles.pageHeader}>
|
||||
<h1 className={styles.pageTitle}>
|
||||
{t(getTranslationID("workflowPage.label.title"))}
|
||||
</h1>
|
||||
<p className={styles.pageTx}>
|
||||
{t(getTranslationID("templateFilePage.label.title"))}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<section className={styles.workflow}>
|
||||
<>
|
||||
<AddTemplateFilePopup
|
||||
onClose={() => {
|
||||
setIsShowAddPopup(false);
|
||||
}}
|
||||
isOpen={isShowAddPopup}
|
||||
/>
|
||||
<div className={styles.wrap}>
|
||||
<Header userName="XXXXXXXX" />
|
||||
<UpdateTokenTimer />
|
||||
<main className={styles.main}>
|
||||
<div>
|
||||
<ul className={`${styles.menuAction} ${styles.worktype}`}>
|
||||
<li>
|
||||
<a
|
||||
href="/workflow"
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
>
|
||||
<img src={undo} alt="" className={styles.menuIcon} />
|
||||
{t(getTranslationID("common.label.return"))}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a className={`${styles.menuLink} ${styles.isActive}`}>
|
||||
<img src={addTemplate} alt="" className={styles.menuIcon} />
|
||||
{t(getTranslationID("templateFilePage.label.addTemplate"))}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<table className={`${styles.table} ${styles.template}`}>
|
||||
<tr className={styles.tableHeader}>
|
||||
<th className={styles.noLine}>
|
||||
{t(getTranslationID("templateFilePage.label.fileName"))}
|
||||
</th>
|
||||
<th>{/** empty th */}</th>
|
||||
</tr>
|
||||
{templates?.map((template) => (
|
||||
<tr key={template.id}>
|
||||
<td>{template.name}</td>
|
||||
<td>
|
||||
<ul className={`${styles.menuAction} ${styles.inTable}`}>
|
||||
<li>
|
||||
<a
|
||||
href=""
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
>
|
||||
{t(getTranslationID("common.label.delete"))}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{!isLoading && templates?.length === 0 && (
|
||||
<p
|
||||
style={{
|
||||
margin: "10px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{t(getTranslationID("common.message.listEmpty"))}
|
||||
</p>
|
||||
)}
|
||||
{isLoading && (
|
||||
<img
|
||||
src={progress_activit}
|
||||
className={styles.icLoading}
|
||||
alt="Loading"
|
||||
/>
|
||||
)}
|
||||
</table>
|
||||
<div className={styles.pageHeader}>
|
||||
<h1 className={styles.pageTitle}>
|
||||
{t(getTranslationID("workflowPage.label.title"))}
|
||||
</h1>
|
||||
<p className={styles.pageTx}>
|
||||
{t(getTranslationID("templateFilePage.label.title"))}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
<section className={styles.workflow}>
|
||||
<div>
|
||||
<ul className={`${styles.menuAction} ${styles.worktype}`}>
|
||||
<li>
|
||||
<a
|
||||
href="/workflow"
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
>
|
||||
<img src={undo} alt="" className={styles.menuIcon} />
|
||||
{t(getTranslationID("common.label.return"))}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
onClick={() => {
|
||||
setIsShowAddPopup(true);
|
||||
}}
|
||||
>
|
||||
<img src={addTemplate} alt="" className={styles.menuIcon} />
|
||||
{t(getTranslationID("templateFilePage.label.addTemplate"))}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<table className={`${styles.table} ${styles.template}`}>
|
||||
<tr className={styles.tableHeader}>
|
||||
<th className={styles.noLine}>
|
||||
{t(getTranslationID("templateFilePage.label.fileName"))}
|
||||
</th>
|
||||
<th>{/** empty th */}</th>
|
||||
</tr>
|
||||
{templates?.map((template) => (
|
||||
<tr key={template.id}>
|
||||
<td>{template.name}</td>
|
||||
<td>
|
||||
<ul className={`${styles.menuAction} ${styles.inTable}`}>
|
||||
<li>
|
||||
<a
|
||||
href=""
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
>
|
||||
{t(getTranslationID("common.label.delete"))}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{!isLoading && templates?.length === 0 && (
|
||||
<p
|
||||
style={{
|
||||
margin: "10px",
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
{t(getTranslationID("common.message.listEmpty"))}
|
||||
</p>
|
||||
)}
|
||||
{isLoading && (
|
||||
<img
|
||||
src={progress_activit}
|
||||
className={styles.icLoading}
|
||||
alt="Loading"
|
||||
/>
|
||||
)}
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
14
dictation_client/src/styles/app.module.scss.d.ts
vendored
14
dictation_client/src/styles/app.module.scss.d.ts
vendored
@ -22,6 +22,7 @@ declare const classNames: {
|
||||
readonly buttonText: "buttonText";
|
||||
readonly formList: "formList";
|
||||
readonly formTitle: "formTitle";
|
||||
readonly alignCenter: "alignCenter";
|
||||
readonly overLine: "overLine";
|
||||
readonly full: "full";
|
||||
readonly hasbg: "hasbg";
|
||||
@ -38,10 +39,13 @@ declare const classNames: {
|
||||
readonly formError: "formError";
|
||||
readonly formConfirm: "formConfirm";
|
||||
readonly formSubmit: "formSubmit";
|
||||
readonly formButtonFul: "formButtonFul";
|
||||
readonly formButton: "formButton";
|
||||
readonly formDelete: "formDelete";
|
||||
readonly formBack: "formBack";
|
||||
readonly formButtonTx: "formButtonTx";
|
||||
readonly formDone: "formDone";
|
||||
readonly formTrash: "formTrash";
|
||||
readonly listVertical: "listVertical";
|
||||
readonly listHeader: "listHeader";
|
||||
readonly boxFlex: "boxFlex";
|
||||
@ -109,6 +113,11 @@ declare const classNames: {
|
||||
readonly isDisable: "isDisable";
|
||||
readonly icCheckCircle: "icCheckCircle";
|
||||
readonly icInTable: "icInTable";
|
||||
readonly manage: "manage";
|
||||
readonly manageInfo: "manageInfo";
|
||||
readonly txNormal: "txNormal";
|
||||
readonly manageIcon: "manageIcon";
|
||||
readonly manageIconClose: "manageIconClose";
|
||||
readonly history: "history";
|
||||
readonly cardHistory: "cardHistory";
|
||||
readonly partner: "partner";
|
||||
@ -116,6 +125,7 @@ declare const classNames: {
|
||||
readonly displayOptions: "displayOptions";
|
||||
readonly tableFilter: "tableFilter";
|
||||
readonly tableFilter2: "tableFilter2";
|
||||
readonly mnBack: "mnBack";
|
||||
readonly txWsline: "txWsline";
|
||||
readonly hidePri: "hidePri";
|
||||
readonly opPri: "opPri";
|
||||
@ -175,6 +185,7 @@ declare const classNames: {
|
||||
readonly op9: "op9";
|
||||
readonly hideO10: "hideO10";
|
||||
readonly op10: "op10";
|
||||
readonly property: "property";
|
||||
readonly formChange: "formChange";
|
||||
readonly chooseMember: "chooseMember";
|
||||
readonly holdMember: "holdMember";
|
||||
@ -184,7 +195,6 @@ declare const classNames: {
|
||||
readonly template: "template";
|
||||
readonly worktype: "worktype";
|
||||
readonly selectMenu: "selectMenu";
|
||||
readonly alignCenter: "alignCenter";
|
||||
readonly alignLeft: "alignLeft";
|
||||
readonly alignRight: "alignRight";
|
||||
readonly floatNone: "floatNone";
|
||||
@ -206,9 +216,7 @@ declare const classNames: {
|
||||
readonly paddSide1: "paddSide1";
|
||||
readonly paddSide2: "paddSide2";
|
||||
readonly paddSide3: "paddSide3";
|
||||
readonly txNormal: "txNormal";
|
||||
readonly txIcon: "txIcon";
|
||||
readonly txWswrap: "txWswrap";
|
||||
readonly required: "required";
|
||||
};
|
||||
export = classNames;
|
||||
|
||||
@ -417,7 +417,12 @@
|
||||
"label": {
|
||||
"title": "(de)Template List",
|
||||
"addTemplate": "(de)Add Template",
|
||||
"fileName": "(de)Flie Name"
|
||||
"fileName": "(de)Flie Name",
|
||||
"chooseFile": "(de)Choose File",
|
||||
"notFileChosen": "(de)- Not file chosen -",
|
||||
"fileSizeTerms": "(de)Flie Name",
|
||||
"fileSizeError": "(de)選択されたファイルのサイズが大きすぎます。サイズが?MB以下のファイルを選択してください。",
|
||||
"fileEmptyError": "(de)ファイル選択は必須です。ファイルを選択してください。"
|
||||
}
|
||||
},
|
||||
"partnerPage": {
|
||||
@ -468,4 +473,4 @@
|
||||
"cancelButton": "(de)Cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -417,7 +417,12 @@
|
||||
"label": {
|
||||
"title": "Template List",
|
||||
"addTemplate": "Add Template",
|
||||
"fileName": "Flie Name"
|
||||
"fileName": "Flie Name",
|
||||
"chooseFile": "Choose File",
|
||||
"notFileChosen": "- Not file chosen -",
|
||||
"fileSizeTerms": "Flie Name",
|
||||
"fileSizeError": "選択されたファイルのサイズが大きすぎます。サイズが?MB以下のファイルを選択してください。",
|
||||
"fileEmptyError": "ファイル選択は必須です。ファイルを選択してください。"
|
||||
}
|
||||
},
|
||||
"partnerPage": {
|
||||
@ -468,4 +473,4 @@
|
||||
"cancelButton": "Cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -417,7 +417,12 @@
|
||||
"label": {
|
||||
"title": "(es)Template List",
|
||||
"addTemplate": "(es)Add Template",
|
||||
"fileName": "(es)Flie Name"
|
||||
"fileName": "(es)Flie Name",
|
||||
"chooseFile": "(es)Choose File",
|
||||
"notFileChosen": "(es)- Not file chosen -",
|
||||
"fileSizeTerms": "(es)Flie Name",
|
||||
"fileSizeError": "(es)選択されたファイルのサイズが大きすぎます。サイズが?MB以下のファイルを選択してください。",
|
||||
"fileEmptyError": "(es)ファイル選択は必須です。ファイルを選択してください。"
|
||||
}
|
||||
},
|
||||
"partnerPage": {
|
||||
@ -468,4 +473,4 @@
|
||||
"cancelButton": "(es)Cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -417,7 +417,12 @@
|
||||
"label": {
|
||||
"title": "(fr)Template List",
|
||||
"addTemplate": "(fr)Add Template",
|
||||
"fileName": "(fr)Flie Name"
|
||||
"fileName": "(fr)Flie Name",
|
||||
"chooseFile": "(fr)Choose File",
|
||||
"notFileChosen": "(fr)- Not file chosen -",
|
||||
"fileSizeTerms": "(fr)Flie Name",
|
||||
"fileSizeError": "(fr)選択されたファイルのサイズが大きすぎます。サイズが?MB以下のファイルを選択してください。",
|
||||
"fileEmptyError": "(fr)ファイル選択は必須です。ファイルを選択してください。"
|
||||
}
|
||||
},
|
||||
"partnerPage": {
|
||||
@ -468,4 +473,4 @@
|
||||
"cancelButton": "(fr)Cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user