From 8793606070aab4b2eb213a507fde98112a4f1dd1 Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Tue, 16 Jan 2024 00:17:45 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20682:=20=E3=82=BF=E3=82=B9?= =?UTF-8?q?=E3=82=AF=E4=B8=80=E8=A6=A7=E7=94=BB=E9=9D=A2=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3458: タスク一覧画面修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3458) - タスク一覧画面のタスク削除ボタンからタスクを削除する処理を実装しました。 - タスクがInProgress、ユーザーがTypistの場合にはボタンを非活性となるようにしています。 ## レビューポイント - エラーごとの処理内容は適切でしょうか? - ボタンの活性制御は適切でしょうか? ## UIの変更 - なし ## 動作確認状況 - ローカルで確認 --- dictation_client/src/api/api.ts | 73 +++++++++++++++++++ .../src/features/dictation/constants.ts | 2 +- .../src/features/dictation/dictationSlice.ts | 10 +++ .../src/features/dictation/operations.ts | 72 ++++++++++++++++++ .../src/pages/DictationPage/index.tsx | 61 +++++++++++++++- dictation_client/src/styles/app.module.scss | 6 +- dictation_client/src/translation/de.json | 5 +- dictation_client/src/translation/en.json | 5 +- dictation_client/src/translation/es.json | 5 +- dictation_client/src/translation/fr.json | 5 +- .../migrations/051-delete-license-alert.sql | 6 ++ 11 files changed, 237 insertions(+), 13 deletions(-) create mode 100644 dictation_server/db/migrations/051-delete-license-alert.sql diff --git a/dictation_client/src/api/api.ts b/dictation_client/src/api/api.ts index 645f058..c50df11 100644 --- a/dictation_client/src/api/api.ts +++ b/dictation_client/src/api/api.ts @@ -5984,6 +5984,44 @@ export const TasksApiAxiosParamCreator = function (configuration?: Configuration + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * 指定した文字起こしタスクを削除します。 + * @summary + * @param {number} audioFileId ODMS Cloud上の音声ファイルID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteTask: async (audioFileId: number, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'audioFileId' is not null or undefined + assertParamExists('deleteTask', 'audioFileId', audioFileId) + const localVarPath = `/tasks/{audioFileId}/delete` + .replace(`{${"audioFileId"}}`, encodeURIComponent(String(audioFileId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -6207,6 +6245,19 @@ export const TasksApiFp = function(configuration?: Configuration) { const operationBasePath = operationServerMap['TasksApi.checkout']?.[index]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath); }, + /** + * 指定した文字起こしタスクを削除します。 + * @summary + * @param {number} audioFileId ODMS Cloud上の音声ファイルID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deleteTask(audioFileId: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deleteTask(audioFileId, options); + const index = configuration?.serverIndex ?? 0; + const operationBasePath = operationServerMap['TasksApi.deleteTask']?.[index]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath); + }, /** * 指定した文字起こしタスクの次のタスクに紐づく音声ファイルIDを取得します * @summary @@ -6311,6 +6362,16 @@ export const TasksApiFactory = function (configuration?: Configuration, basePath checkout(audioFileId: number, options?: any): AxiosPromise { return localVarFp.checkout(audioFileId, options).then((request) => request(axios, basePath)); }, + /** + * 指定した文字起こしタスクを削除します。 + * @summary + * @param {number} audioFileId ODMS Cloud上の音声ファイルID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteTask(audioFileId: number, options?: any): AxiosPromise { + return localVarFp.deleteTask(audioFileId, options).then((request) => request(axios, basePath)); + }, /** * 指定した文字起こしタスクの次のタスクに紐づく音声ファイルIDを取得します * @summary @@ -6416,6 +6477,18 @@ export class TasksApi extends BaseAPI { return TasksApiFp(this.configuration).checkout(audioFileId, options).then((request) => request(this.axios, this.basePath)); } + /** + * 指定した文字起こしタスクを削除します。 + * @summary + * @param {number} audioFileId ODMS Cloud上の音声ファイルID + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TasksApi + */ + public deleteTask(audioFileId: number, options?: AxiosRequestConfig) { + return TasksApiFp(this.configuration).deleteTask(audioFileId, options).then((request) => request(this.axios, this.basePath)); + } + /** * 指定した文字起こしタスクの次のタスクに紐づく音声ファイルIDを取得します * @summary diff --git a/dictation_client/src/features/dictation/constants.ts b/dictation_client/src/features/dictation/constants.ts index de262e0..69eae60 100644 --- a/dictation_client/src/features/dictation/constants.ts +++ b/dictation_client/src/features/dictation/constants.ts @@ -8,7 +8,7 @@ export const STATUS = { export type StatusType = typeof STATUS[keyof typeof STATUS]; -export const LIMIT_TASK_NUM = 20; +export const LIMIT_TASK_NUM = 100; export const SORTABLE_COLUMN = { JobNumber: "JOB_NUMBER", diff --git a/dictation_client/src/features/dictation/dictationSlice.ts b/dictation_client/src/features/dictation/dictationSlice.ts index c434639..b187c88 100644 --- a/dictation_client/src/features/dictation/dictationSlice.ts +++ b/dictation_client/src/features/dictation/dictationSlice.ts @@ -11,6 +11,7 @@ import { playbackAsync, updateAssigneeAsync, cancelAsync, + deleteTaskAsync, } from "./operations"; import { SORTABLE_COLUMN, @@ -218,6 +219,15 @@ export const dictationSlice = createSlice({ builder.addCase(backupTasksAsync.rejected, (state) => { state.apps.isDownloading = false; }); + builder.addCase(deleteTaskAsync.pending, (state) => { + state.apps.isLoading = true; + }); + builder.addCase(deleteTaskAsync.fulfilled, (state) => { + state.apps.isLoading = false; + }); + builder.addCase(deleteTaskAsync.rejected, (state) => { + state.apps.isLoading = false; + }); }, }); diff --git a/dictation_client/src/features/dictation/operations.ts b/dictation_client/src/features/dictation/operations.ts index 23b98f4..5bf50da 100644 --- a/dictation_client/src/features/dictation/operations.ts +++ b/dictation_client/src/features/dictation/operations.ts @@ -572,3 +572,75 @@ export const backupTasksAsync = createAsyncThunk< return thunkApi.rejectWithValue({ error }); } }); + +export const deleteTaskAsync = createAsyncThunk< + { + // empty + }, + { + // パラメータ + audioFileId: number; + }, + { + // rejectした時の返却値の型 + rejectValue: { + error: ErrorObject; + }; + } +>("dictations/deleteTaskAsync", async (args, thunkApi) => { + const { audioFileId } = args; + + // apiのConfigurationを取得する + const { getState } = thunkApi; + const state = getState() as RootState; + const { configuration } = state.auth; + const accessToken = getAccessToken(state.auth); + const config = new Configuration(configuration); + const tasksApi = new TasksApi(config); + + try { + await tasksApi.deleteTask(audioFileId, { + headers: { authorization: `Bearer ${accessToken}` }, + }); + + thunkApi.dispatch( + openSnackbar({ + level: "info", + message: getTranslationID("common.message.success"), + }) + ); + return {}; + } catch (e) { + // e ⇒ errorObjectに変換" + const error = createErrorObject(e); + + let message = getTranslationID("dictationPage.message.backupFailedError"); + + if (error.statusCode === 400) { + if (error.code === "E010603") { + // タスクが削除済みの場合は成功扱いとする + thunkApi.dispatch( + openSnackbar({ + level: "info", + message: getTranslationID("common.message.success"), + }) + ); + return {}; + } + + if (error.code === "E010601") { + // タスクがInprogressの場合はエラー + message = getTranslationID("dictationPage.message.deleteFailedError"); + } + } + + thunkApi.dispatch( + openSnackbar({ + level: "error", + message, + }) + ); + + return thunkApi.rejectWithValue({ error }); + } +}); diff --git a/dictation_client/src/pages/DictationPage/index.tsx b/dictation_client/src/pages/DictationPage/index.tsx index b2591d3..b024fd4 100644 --- a/dictation_client/src/pages/DictationPage/index.tsx +++ b/dictation_client/src/pages/DictationPage/index.tsx @@ -33,6 +33,7 @@ import { playbackAsync, cancelAsync, PRIORITY, + deleteTaskAsync, } from "features/dictation"; import { getTranslationID } from "translation"; import { Task } from "api/api"; @@ -61,6 +62,8 @@ const DictationPage: React.FC = (): JSX.Element => { const isTypist = isTypistUser(); const isNone = !isAuthor && !isTypist; + const isDeletableRole = isAdmin || isAuthor; + // popup制御関係 const [ isChangeTranscriptionistPopupOpen, @@ -506,6 +509,53 @@ const DictationPage: React.FC = (): JSX.Element => { return styles.isActiveAz; }; + const onDeleteTask = useCallback( + async (audioFileId: number) => { + if ( + /* eslint-disable-next-line no-alert */ + !window.confirm(t(getTranslationID("common.message.dialogConfirm"))) + ) { + return; + } + const { meta } = await dispatch( + deleteTaskAsync({ + audioFileId, + }) + ); + if (meta.requestStatus === "fulfilled") { + const filter = getFilter( + filterUploaded, + filterInProgress, + filterPending, + filterFinished, + filterBackup + ); + dispatch( + listTasksAsync({ + limit: LIMIT_TASK_NUM, + offset: 0, + filter, + direction: sortDirection, + paramName: sortableParamName, + }) + ); + dispatch(listTypistsAsync()); + dispatch(listTypistGroupsAsync()); + } + }, + [ + dispatch, + filterBackup, + filterFinished, + filterInProgress, + filterPending, + filterUploaded, + sortDirection, + sortableParamName, + t, + ] + ); + // 初回読み込み処理 useEffect(() => { (async () => { @@ -1150,7 +1200,16 @@ const DictationPage: React.FC = (): JSX.Element => {
  • - + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */} + onDeleteTask(x.audioFileId)} + > {t( getTranslationID( "dictationPage.label.deleteDictation" diff --git a/dictation_client/src/styles/app.module.scss b/dictation_client/src/styles/app.module.scss index f5399f2..2435346 100644 --- a/dictation_client/src/styles/app.module.scss +++ b/dictation_client/src/styles/app.module.scss @@ -2047,7 +2047,7 @@ tr.isSelected .menuInTable li a.isDisable { position: sticky; top: 0; background: #282828; - z-index: 1; + z-index: 3; } .dictation .table.dictation tr.tableHeader th.clm0 { width: 0px; @@ -2482,8 +2482,8 @@ tr.isSelected .menuInTable li a.isDisable { } .formChange ul.chooseMember li input:checked + label:hover, .formChange ul.holdMember li input:checked + label:hover { - background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat - right center; + background: #e6e6e6 url(../assets/images/arrow_circle_right.svg) no-repeat right + center; background-size: 1.3rem; } .formChange > p { diff --git a/dictation_client/src/translation/de.json b/dictation_client/src/translation/de.json index 011bfb2..6b1395d 100644 --- a/dictation_client/src/translation/de.json +++ b/dictation_client/src/translation/de.json @@ -206,7 +206,8 @@ "taskToPlaybackNoExists": "Die Datei kann nicht abgespielt werden, da sie bereits transkribiert wurde oder nicht existiert.", "taskNotEditable": "Der Transkriptionist kann nicht geändert werden, da die Transkription bereits ausgeführt wird oder die Datei nicht vorhanden ist. Bitte aktualisieren Sie den Bildschirm und prüfen Sie den aktuellen Status.", "backupFailedError": "(de)ファイルのバックアップに失敗したため処理を中断しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。", - "cancelFailedError": "(de)タスクのキャンセルに失敗しました。画面を更新し、再度ご確認ください。" + "cancelFailedError": "(de)タスクのキャンセルに失敗しました。画面を更新し、再度ご確認ください。", + "deleteFailedError": "(de)タスクの削除に失敗しました。画面を更新し、再度ご確認ください。" }, "label": { "title": "Diktate", @@ -555,4 +556,4 @@ "close": "(de)Close" } } -} \ No newline at end of file +} diff --git a/dictation_client/src/translation/en.json b/dictation_client/src/translation/en.json index c541797..e18c689 100644 --- a/dictation_client/src/translation/en.json +++ b/dictation_client/src/translation/en.json @@ -206,7 +206,8 @@ "taskToPlaybackNoExists": "The file cannot be played because it has already been transcribed or does not exist.", "taskNotEditable": "The transcriptionist cannot be changed because the transcription is already in progress or the file does not exist. Please refresh the screen and check the latest status.", "backupFailedError": "ファイルのバックアップに失敗したため処理を中断しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。", - "cancelFailedError": "タスクのキャンセルに失敗しました。画面を更新し、再度ご確認ください。" + "cancelFailedError": "タスクのキャンセルに失敗しました。画面を更新し、再度ご確認ください。", + "deleteFailedError": "タスクの削除に失敗しました。画面を更新し、再度ご確認ください。" }, "label": { "title": "Dictations", @@ -555,4 +556,4 @@ "close": "Close" } } -} \ No newline at end of file +} diff --git a/dictation_client/src/translation/es.json b/dictation_client/src/translation/es.json index 6726027..36d8ff6 100644 --- a/dictation_client/src/translation/es.json +++ b/dictation_client/src/translation/es.json @@ -206,7 +206,8 @@ "taskToPlaybackNoExists": "El archivo no se puede reproducir porque ya ha sido transcrito o no existe.", "taskNotEditable": "No se puede cambiar el transcriptor porque la transcripción ya está en curso o el archivo no existe. Actualice la pantalla y verifique el estado más reciente.", "backupFailedError": "(es)ファイルのバックアップに失敗したため処理を中断しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。", - "cancelFailedError": "(es)タスクのキャンセルに失敗しました。画面を更新し、再度ご確認ください。" + "cancelFailedError": "(es)タスクのキャンセルに失敗しました。画面を更新し、再度ご確認ください。", + "deleteFailedError": "(es)タスクの削除に失敗しました。画面を更新し、再度ご確認ください。" }, "label": { "title": "Dictado", @@ -555,4 +556,4 @@ "close": "(es)Close" } } -} \ No newline at end of file +} diff --git a/dictation_client/src/translation/fr.json b/dictation_client/src/translation/fr.json index 4e3fbbc..b13a338 100644 --- a/dictation_client/src/translation/fr.json +++ b/dictation_client/src/translation/fr.json @@ -206,7 +206,8 @@ "taskToPlaybackNoExists": "Le fichier ne peut pas être lu car il a déjà été transcrit ou n'existe pas.", "taskNotEditable": "Le transcripteur ne peut pas être changé car la transcription est déjà en cours ou le fichier n'existe pas. Veuillez actualiser l'écran et vérifier le dernier statut.", "backupFailedError": "(fr)ファイルのバックアップに失敗したため処理を中断しました。時間をおいて再実行しても解決しない場合はシステム管理者にお問い合わせください。", - "cancelFailedError": "(fr)タスクのキャンセルに失敗しました。画面を更新し、再度ご確認ください。" + "cancelFailedError": "(fr)タスクのキャンセルに失敗しました。画面を更新し、再度ご確認ください。", + "deleteFailedError": "(fr)タスクの削除に失敗しました。画面を更新し、再度ご確認ください。" }, "label": { "title": "Dictées", @@ -555,4 +556,4 @@ "close": "(fr)Close" } } -} \ No newline at end of file +} diff --git a/dictation_server/db/migrations/051-delete-license-alert.sql b/dictation_server/db/migrations/051-delete-license-alert.sql new file mode 100644 index 0000000..e191a81 --- /dev/null +++ b/dictation_server/db/migrations/051-delete-license-alert.sql @@ -0,0 +1,6 @@ +-- +migrate Up +ALTER TABLE `users` DROP COLUMN `license_alert`; + + +-- +migrate Down +ALTER TABLE `users` ADD COLUMN `license_alert` BOOLEAN DEFAULT TRUE NOT NULL COMMENT 'ライセンスの期限切れ通知をするかどうか'; \ No newline at end of file