Merged PR 682: タスク一覧画面修正

## 概要
[Task3458: タスク一覧画面修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3458)

- タスク一覧画面のタスク削除ボタンからタスクを削除する処理を実装しました。
  - タスクがInProgress、ユーザーがTypistの場合にはボタンを非活性となるようにしています。

## レビューポイント
- エラーごとの処理内容は適切でしょうか?
- ボタンの活性制御は適切でしょうか?

## UIの変更
- なし
## 動作確認状況
- ローカルで確認
This commit is contained in:
makabe.t 2024-01-16 00:17:45 +00:00
parent 81c299dd99
commit 8793606070
11 changed files with 237 additions and 13 deletions

View File

@ -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<RequestArgs> => {
// 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<object>> {
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<object> {
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<object> {
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

View File

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

View File

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

View File

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

View File

@ -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 => {
</a>
</li>
<li>
<a>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={
isDeletableRole &&
x.status !== STATUS.INPROGRESS
? ""
: styles.isDisable
}
onClick={() => onDeleteTask(x.audioFileId)}
>
{t(
getTranslationID(
"dictationPage.label.deleteDictation"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 'ライセンスの期限切れ通知をするかどうか';