2025-01-21 05:23:54 +00:00

1877 lines
68 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useCallback, useEffect, useState } from "react";
import { AppDispatch } from "app/store";
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
import styles from "styles/app.module.scss";
import Footer from "components/footer";
import Header from "components/header";
import {
listTasksAsync,
selectCurrentPage,
selectTasks,
selectTotal,
selectTotalPage,
DIRECTION,
DisplayInfoType,
INIT_DISPLAY_INFO,
SORTABLE_COLUMN,
selectDisplayInfo,
changeDisplayInfo,
getSortColumnAsync,
selectParamName,
selectDirection,
changeParamName,
changeDirection,
changeAuthorId,
changeFileName,
changeSelectedTask,
openFilePropertyInfo,
SortableColumnType,
changeAssignee,
listTypistsAsync,
listTypistGroupsAsync,
DirectionType,
selectIsLoading,
playbackAsync,
cancelAsync,
reopenAsync,
PRIORITY,
deleteTaskAsync,
isSortableColumnType,
isDirectionType,
getTaskFiltersAsync,
selectAuthorId,
selectFilename,
updateTaskFiltersAsync,
updateSortColumnAsync,
} from "features/dictation";
import { getTranslationID } from "translation";
import { Task } from "api/api";
import { isAdminUser, isAuthorUser, isTypistUser } from "features/auth";
import { STATUS, LIMIT_TASK_NUM } from "../../features/dictation";
import uploaded from "../../assets/images/uploaded.svg";
import pending from "../../assets/images/pending.svg";
import inprogress from "../../assets/images/inprogress.svg";
import finished from "../../assets/images/finished.svg";
import backup from "../../assets/images/backup.svg";
import lock from "../../assets/images/lock.svg";
import progress_activit from "../../assets/images/progress_activit.svg";
import download from "../../assets/images/download.svg";
import open_in_new from "../../assets/images/open_in_new.svg";
import { DisPlayInfo } from "./displayInfo";
import { ChangeTranscriptionistPopup } from "./changeTranscriptionistPopup";
import { BackupPopup } from "./backupPopup";
import { FilePropertyPopup } from "./filePropertyPopup";
import searchIcon from "../../assets/images/search.svg";
const DictationPage: React.FC = (): JSX.Element => {
const dispatch: AppDispatch = useDispatch();
const [t] = useTranslation();
const isAdmin = isAdminUser();
const isAuthor = isAuthorUser();
const isTypist = isTypistUser();
const isNone = !isAuthor && !isTypist;
const isDeletableRole = isAdmin || isAuthor;
// popup制御関係
const [
isChangeTranscriptionistPopupOpen,
setIsChangeTranscriptionistPopupOpen,
] = useState(false);
const [isFilePropertyPopupOpen, setIsFilePropertyPopupOpen] = useState(false);
const [isBackupPopupOpen, setIsBackupPopupOpen] = useState(false);
const onChangeTranscriptionistPopupOpen = useCallback(
(task: Task) => {
dispatch(changeAssignee({ selected: task.assignees }));
dispatch(changeSelectedTask({ task }));
setIsChangeTranscriptionistPopupOpen(true);
},
[dispatch, setIsChangeTranscriptionistPopupOpen]
);
const onClickFileProperty = useCallback(
(task: Task) => {
dispatch(openFilePropertyInfo({ task }));
setIsFilePropertyPopupOpen(true);
},
[dispatch, setIsFilePropertyPopupOpen]
);
// 各カラムの表示/非表示
const displayColumn = useSelector(selectDisplayInfo);
// フィルターするステータス
const [filterUploaded, setFilterUploaded] = useState(true);
const [filterPending, setFilterPending] = useState(true);
const [filterInProgress, setFilterInProgress] = useState(true);
const [filterFinished, setFilterFinished] = useState(true);
const [filterBackup, setFilterBackup] = useState(false);
// 検索条件の入力値
const [filterConditionAuthorId, setFilterConditionAuthorId] = useState("");
const [filterConditionFileName, setFilterConditionFileName] = useState("");
// ソート対象カラム
const sortableParamName = useSelector(selectParamName);
const sortDirection = useSelector(selectDirection);
// task_filtersテーブルの検索条件
const authorId = useSelector(selectAuthorId);
const fileName = useSelector(selectFilename);
const tasks = useSelector(selectTasks);
const total = useSelector(selectTotal);
const totalPage = useSelector(selectTotalPage);
const currentPage = useSelector(selectCurrentPage);
const isLoading = useSelector(selectIsLoading);
// ページネーションのボタンクリック時のアクション
const getFirstPage = useCallback(() => {
const filter = getFilter(
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup
);
dispatch(
listTasksAsync({
limit: LIMIT_TASK_NUM,
offset: 0,
filter,
direction: sortDirection,
paramName: sortableParamName,
authorId,
fileName,
})
);
dispatch(listTypistsAsync());
dispatch(listTypistGroupsAsync());
}, [
dispatch,
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup,
sortDirection,
sortableParamName,
authorId,
fileName,
]);
const getLastPage = useCallback(() => {
const filter = getFilter(
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup
);
const lastPageOffset = (totalPage - 1) * LIMIT_TASK_NUM;
dispatch(
listTasksAsync({
limit: LIMIT_TASK_NUM,
offset: lastPageOffset,
filter,
direction: sortDirection,
paramName: sortableParamName,
authorId,
fileName,
})
);
dispatch(listTypistsAsync());
dispatch(listTypistGroupsAsync());
}, [
dispatch,
totalPage,
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup,
sortDirection,
sortableParamName,
authorId,
fileName,
]);
const getPrevPage = useCallback(() => {
const filter = getFilter(
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup
);
const prevPageOffset = (currentPage - 2) * LIMIT_TASK_NUM;
dispatch(
listTasksAsync({
limit: LIMIT_TASK_NUM,
offset: prevPageOffset,
filter,
direction: sortDirection,
paramName: sortableParamName,
authorId,
fileName,
})
);
dispatch(listTypistsAsync());
dispatch(listTypistGroupsAsync());
}, [
dispatch,
currentPage,
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup,
sortDirection,
sortableParamName,
authorId,
fileName,
]);
const getNextPage = useCallback(() => {
const filter = getFilter(
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup
);
const nextPageOffset = currentPage * LIMIT_TASK_NUM;
dispatch(
listTasksAsync({
limit: LIMIT_TASK_NUM,
offset: nextPageOffset,
filter,
direction: sortDirection,
paramName: sortableParamName,
authorId,
fileName,
})
);
dispatch(listTypistsAsync());
dispatch(listTypistGroupsAsync());
}, [
dispatch,
currentPage,
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup,
sortDirection,
sortableParamName,
authorId,
fileName,
]);
const updateSortColumn = useCallback(
(paramName: SortableColumnType) => {
const currentDirection =
sortableParamName === paramName && sortDirection === DIRECTION.ASC
? DIRECTION.DESC
: DIRECTION.ASC;
dispatch(changeDirection({ direction: currentDirection }));
dispatch(changeParamName({ paramName }));
// ローカルストレージにソート情報を保存する
localStorage.setItem(
"sortCriteria",
`direction:${currentDirection},paramName:${paramName}`
);
const filter = getFilter(
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup
);
dispatch(
listTasksAsync({
limit: LIMIT_TASK_NUM,
offset: 0,
filter,
direction: currentDirection,
paramName,
authorId,
fileName,
})
);
dispatch(listTypistsAsync());
dispatch(listTypistGroupsAsync());
},
[
dispatch,
sortableParamName,
sortDirection,
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup,
authorId,
fileName,
]
);
const getFilter = (
hasUploaded: boolean,
hasInProgress: boolean,
hasPending: boolean,
hasFinished: boolean,
hasBackup: boolean
): string | undefined => {
const filterStatus = [];
if (hasUploaded) {
filterStatus.push(STATUS.UPLOADED);
}
if (hasInProgress) {
filterStatus.push(STATUS.INPROGRESS);
}
if (hasPending) {
filterStatus.push(STATUS.PENDING);
}
if (hasFinished) {
filterStatus.push(STATUS.FINISHED);
}
if (hasBackup) {
filterStatus.push(STATUS.BACKUP);
}
if (filterStatus.length === 0) {
return undefined;
}
return filterStatus.join(",");
};
// ステータスフィルターの変更時アクション
const updateFilter = useCallback(
(
hasUploaded: boolean,
hasInProgress: boolean,
hasPending: boolean,
hasFinished: boolean,
hasBackup: boolean
) => {
const filter = getFilter(
hasUploaded,
hasInProgress,
hasPending,
hasFinished,
hasBackup
);
// フィルターの状態をローカルストレージに保存する
localStorage.setItem(
"filterCriteria",
JSON.stringify({
Uploaded: hasUploaded,
InProgress: hasInProgress,
Pending: hasPending,
Finished: hasFinished,
Backup: hasBackup,
})
);
dispatch(
listTasksAsync({
limit: LIMIT_TASK_NUM,
offset: 0,
filter,
direction: sortDirection,
paramName: sortableParamName,
authorId,
fileName,
})
);
dispatch(listTypistsAsync());
dispatch(listTypistGroupsAsync());
},
[dispatch, sortDirection, sortableParamName, authorId, fileName]
);
const onPlayBack = useCallback(
async (audioFileId: number) => {
if (
/* eslint-disable-next-line no-alert */
!window.confirm(t(getTranslationID("common.message.dialogConfirm")))
) {
return;
}
const { meta } = await dispatch(
playbackAsync({
audioFileId,
direction: sortDirection,
paramName: sortableParamName,
filterConditionAuthorId: authorId,
filterConditionFileName: fileName,
})
);
if (meta.requestStatus === "fulfilled") {
// ローカルストレージにソート情報を削除する
localStorage.removeItem("sortCriteria");
const filter = getFilter(
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup
);
dispatch(
listTasksAsync({
limit: LIMIT_TASK_NUM,
offset: 0,
filter,
direction: sortDirection,
paramName: sortableParamName,
authorId,
fileName,
})
);
dispatch(listTypistsAsync());
dispatch(listTypistGroupsAsync());
const url = `${import.meta.env.VITE_DESK_TOP_APP_SCHEME
}:playback?audioId=${audioFileId}`;
const a = document.createElement("a");
a.href = url;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
},
[
dispatch,
filterBackup,
filterFinished,
filterInProgress,
filterPending,
filterUploaded,
sortDirection,
sortableParamName,
authorId,
fileName,
t,
]
);
const onClosePopup = useCallback(
(isChanged: boolean) => {
if (isChanged) {
const filter = getFilter(
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup
);
dispatch(
listTasksAsync({
limit: LIMIT_TASK_NUM,
offset: 0,
filter,
direction: sortDirection,
paramName: sortableParamName,
authorId,
fileName,
})
);
}
setIsChangeTranscriptionistPopupOpen(false);
},
[
dispatch,
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup,
sortDirection,
sortableParamName,
authorId,
fileName,
]
);
const onCancel = useCallback(
async (audioFileId: number) => {
if (
/* eslint-disable-next-line no-alert */
!window.confirm(t(getTranslationID("common.message.dialogConfirm")))
) {
return;
}
const { meta } = await dispatch(
cancelAsync({
audioFileId,
direction: sortDirection,
paramName: sortableParamName,
isTypist,
filterConditionAuthorId: authorId,
filterConditionFileName: fileName,
})
);
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,
authorId,
fileName,
})
);
dispatch(listTypistsAsync());
dispatch(listTypistGroupsAsync());
}
},
[
dispatch,
filterBackup,
filterFinished,
filterInProgress,
filterPending,
filterUploaded,
isTypist,
sortDirection,
sortableParamName,
authorId,
fileName,
t,
]
);
const onReopen = useCallback(
async (audioFileId: number) => {
if (
/* eslint-disable-next-line no-alert */
!window.confirm(t(getTranslationID("common.message.dialogConfirm")))
) {
return;
}
const { meta } = await dispatch(
reopenAsync({
audioFileId,
direction: sortDirection,
paramName: sortableParamName,
isTypist,
filterConditionAuthorId: authorId,
filterConditionFileName: fileName,
})
);
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,
authorId,
fileName,
})
);
dispatch(listTypistsAsync());
dispatch(listTypistGroupsAsync());
}
},
[
dispatch,
filterBackup,
filterFinished,
filterInProgress,
filterPending,
filterUploaded,
isTypist,
sortDirection,
sortableParamName,
authorId,
fileName,
t,
]
);
const onCloseBackupPopup = useCallback(() => {
setIsBackupPopupOpen(false);
}, []);
const onClickBackup = useCallback(() => {
setIsBackupPopupOpen(true);
}, []);
const onCloseFilePropertyPopup = useCallback(
(isChanged: boolean) => {
if (isChanged) {
const filter = getFilter(
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup
);
dispatch(
listTasksAsync({
limit: LIMIT_TASK_NUM,
offset: 0,
filter,
direction: sortDirection,
paramName: sortableParamName,
authorId,
fileName,
})
);
}
setIsFilePropertyPopupOpen(false);
},
[
dispatch,
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup,
sortDirection,
sortableParamName,
authorId,
fileName,
]
);
const onChangeFilterConditionFileName = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setFilterConditionFileName(e.target.value.trimStart());
},
[setFilterConditionFileName]
);
const onChangeFilterConditionAuthorId = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
// 先頭に%が入力されるとWAFのルールでブロックされてしまう。
// Authorの登録時に「_」以外の記号は許可されていないため、「_」と半角英数字以外の文字は除去。
const correctAuthorId = e.target.value.replace(/[^a-zA-Z0-9_]/g, "");
setFilterConditionAuthorId(correctAuthorId);
},
[setFilterConditionAuthorId]
);
const sortIconClass = (
currentParam: SortableColumnType,
currentDirection: DirectionType,
column: SortableColumnType
) => {
if (currentParam !== column) {
return "";
}
if (currentDirection === DIRECTION.DESC) {
return styles.isActiveZa;
}
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,
authorId,
fileName,
})
);
dispatch(listTypistsAsync());
dispatch(listTypistGroupsAsync());
}
},
[
dispatch,
filterBackup,
filterFinished,
filterInProgress,
filterPending,
filterUploaded,
sortDirection,
sortableParamName,
authorId,
fileName,
t,
]
);
const requestSearch = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const { meta: taskFilterMeta } = await dispatch(
updateTaskFiltersAsync({
filterConditionFileName,
filterConditionAuthorId,
})
);
const { meta: sortCriteriaMeta } = await dispatch(
updateSortColumnAsync({
direction: sortDirection,
paramName: sortableParamName,
})
);
if (
taskFilterMeta.requestStatus === "fulfilled" &&
sortCriteriaMeta.requestStatus === "fulfilled"
) {
const filter = getFilter(
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
filterBackup
);
dispatch(changeAuthorId({ authorId: filterConditionAuthorId }));
dispatch(changeFileName({ fileName: filterConditionFileName }));
// 検索した条件でタスク一覧を取得する
dispatch(
listTasksAsync({
limit: LIMIT_TASK_NUM,
offset: 0,
filter,
direction: sortDirection,
paramName: sortableParamName,
authorId: filterConditionAuthorId,
fileName: filterConditionFileName,
})
);
}
},
[
dispatch,
filterBackup,
filterFinished,
filterInProgress,
filterPending,
filterUploaded,
sortDirection,
sortableParamName,
filterConditionAuthorId,
filterConditionFileName,
]
);
// 初回読み込み処理
useEffect(() => {
(async () => {
const displayInfoValue = localStorage.getItem("displayInfo");
let displayInfo: DisplayInfoType;
if (displayInfoValue) {
displayInfo = JSON.parse(displayInfoValue);
} else {
displayInfo = INIT_DISPLAY_INFO;
localStorage.setItem("displayInfo", JSON.stringify(displayInfo));
}
dispatch(changeDisplayInfo({ column: displayInfo }));
// フィルター状態をローカルストレージから取得する
const filterValue = localStorage.getItem("filterCriteria");
let filter: string | undefined;
if (filterValue) {
const parsedFilter = JSON.parse(filterValue);
setFilterUploaded(parsedFilter.Uploaded);
setFilterInProgress(parsedFilter.InProgress);
setFilterPending(parsedFilter.Pending);
setFilterFinished(parsedFilter.Finished);
setFilterBackup(parsedFilter.Backup);
filter = getFilter(
parsedFilter.Uploaded,
parsedFilter.InProgress,
parsedFilter.Pending,
parsedFilter.Finished,
parsedFilter.Backup
);
} else {
filter = getFilter(true, true, true, true, false);
localStorage.setItem(
"filterCriteria",
JSON.stringify({
Uploaded: true,
InProgress: true,
Pending: true,
Finished: true,
Backup: false,
})
);
}
// タスクフィルター条件
const { meta: taskFilterMeta, payload: taskfilterPayload } =
await dispatch(getTaskFiltersAsync());
let payloadAuthorId: string | undefined;
let payloadFileName: string | undefined;
if (
taskFilterMeta.requestStatus === "fulfilled" &&
taskfilterPayload &&
!("error" in taskfilterPayload)
) {
payloadAuthorId = taskfilterPayload.authorId ?? "";
payloadFileName = taskfilterPayload.fileName ?? "";
dispatch(changeAuthorId({ authorId: payloadAuthorId }));
dispatch(changeFileName({ fileName: payloadFileName }));
// 初回表示時に検索フォームにtask_filtersテーブルの値を設定する。
setFilterConditionAuthorId(payloadAuthorId);
setFilterConditionFileName(payloadFileName);
}
// ソート条件
const { meta: sortCriteriaMeta, payload: sortCriteriaPayload } =
await dispatch(getSortColumnAsync());
let direction: DirectionType = "ASC";
let paramName: SortableColumnType = "JOB_NUMBER";
if (
sortCriteriaMeta.requestStatus === "fulfilled" &&
sortCriteriaPayload &&
!("error" in sortCriteriaPayload)
) {
// ソート情報をローカルストレージから取得する
const sortColumnValue = localStorage.getItem("sortCriteria") ?? "";
if (sortColumnValue === "") {
direction = sortCriteriaPayload.direction;
paramName = sortCriteriaPayload.paramName;
} else {
// ソート情報をDirectionとParamNameに分割する
const sortColumn = sortColumnValue?.split(",");
const localStorageDirection = sortColumn[0].split(":")[1] ?? "";
const localStorageParamName = sortColumn[1]?.split(":")[1] ?? "";
// 正常なソート情報がローカルストレージに存在する場合はローカルストレージの情報を使用する
direction = isDirectionType(localStorageDirection)
? localStorageDirection
: sortCriteriaPayload.direction;
paramName = isSortableColumnType(localStorageParamName)
? localStorageParamName
: sortCriteriaPayload.paramName;
dispatch(changeDirection({ direction }));
dispatch(changeParamName({ paramName }));
}
}
// タスク一覧を取得する
if (isDirectionType(direction) && isSortableColumnType(paramName)) {
dispatch(
listTasksAsync({
limit: LIMIT_TASK_NUM,
offset: 0,
filter,
direction,
paramName,
authorId: payloadAuthorId,
fileName: payloadFileName,
})
);
dispatch(listTypistsAsync());
dispatch(listTypistGroupsAsync());
}
})();
}, [dispatch]);
return (
<>
<BackupPopup isOpen={isBackupPopupOpen} onClose={onCloseBackupPopup} />
<FilePropertyPopup
isOpen={isFilePropertyPopupOpen}
onClose={onCloseFilePropertyPopup}
/>
<ChangeTranscriptionistPopup
isOpen={isChangeTranscriptionistPopupOpen}
onClose={onClosePopup}
/>
<div className={styles.wrap}>
<Header />
<main className={styles.main}>
<div className="">
<div className={styles.pageHeader}>
<h1 className={styles.pageTitle}>
{t(getTranslationID("dictationPage.label.title"))}
</h1>
</div>
<section className={styles.dictation}>
<div>
<DisPlayInfo />
<ul className={styles.menuAction}>
<li>
<ul className={styles.tableFilter}>
<li>
{t(getTranslationID("dictationPage.label.filter"))}:
</li>
<li>
<label htmlFor="uploaded">
<input
id="uploaded"
type="checkbox"
value="flUploaded"
className={styles.formCheck}
checked={filterUploaded}
disabled={isLoading}
onChange={(e) => {
setFilterUploaded(e.target.checked);
updateFilter(
e.target.checked,
filterInProgress,
filterPending,
filterFinished,
filterBackup
);
}}
/>
{t(getTranslationID("dictationPage.label.uploaded"))}
</label>
</li>
<li>
<label htmlFor="inProgress">
<input
id="inProgress"
type="checkbox"
value="flInProgress"
className={styles.formCheck}
checked={filterInProgress}
disabled={isLoading}
onChange={(e) => {
setFilterInProgress(e.target.checked);
updateFilter(
filterUploaded,
e.target.checked,
filterPending,
filterFinished,
filterBackup
);
}}
/>
{t(
getTranslationID("dictationPage.label.inProgress")
)}
</label>
</li>
<li>
<label htmlFor="pending">
<input
id="pending"
type="checkbox"
value="flPending"
className={styles.formCheck}
checked={filterPending}
disabled={isLoading}
onChange={(e) => {
setFilterPending(e.target.checked);
updateFilter(
filterUploaded,
filterInProgress,
e.target.checked,
filterFinished,
filterBackup
);
}}
/>
{t(getTranslationID("dictationPage.label.pending"))}
</label>
</li>
<li>
<label htmlFor="finished">
<input
id="finished"
type="checkbox"
value="flFinished"
className={styles.formCheck}
checked={filterFinished}
disabled={isLoading}
onChange={(e) => {
setFilterFinished(e.target.checked);
updateFilter(
filterUploaded,
filterInProgress,
filterPending,
e.target.checked,
filterBackup
);
}}
/>
{t(getTranslationID("dictationPage.label.finished"))}
</label>
</li>
<li>
<label htmlFor="backup">
<input
id="backup"
type="checkbox"
value="flBackup"
className={styles.formCheck}
checked={filterBackup}
disabled={isLoading}
onChange={(e) => {
setFilterBackup(e.target.checked);
updateFilter(
filterUploaded,
filterInProgress,
filterPending,
filterFinished,
e.target.checked
);
}}
/>
{t(getTranslationID("dictationPage.label.backup"))}
</label>
</li>
</ul>
</li>
<li className={styles.floatRight}>
<form
className={styles.searchBar}
onSubmit={(e) => requestSearch(e)}
>
<input
type="text"
placeholder={t(
getTranslationID("dictationPage.label.fileName")
)}
value={filterConditionFileName}
onChange={(e) => onChangeFilterConditionFileName(e)}
className={styles.searchInput}
/>
<input
type="text"
placeholder={t(
getTranslationID("dictationPage.label.authorId")
)}
value={filterConditionAuthorId}
onChange={(e) => onChangeFilterConditionAuthorId(e)}
className={styles.searchInput}
/>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<button
type="submit"
className={`${styles.menuLink} ${!isLoading ? styles.isActive : ""
}`}
>
<img
src={searchIcon}
alt="search"
className={styles.menuIcon}
/>
{t(getTranslationID("dictationPage.label.search"))}
</button>
</form>
</li>
</ul>
<div className={styles.tableWrap}>
<table className={`${styles.table} ${styles.dictation}`}>
<tr className={styles.tableHeader}>
<th className={styles.clm0}>{/** th is empty */}</th>
{displayColumn.JobNumber && (
<th className={styles.clm1}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.hasSort} ${sortIconClass(
sortableParamName,
sortDirection,
SORTABLE_COLUMN.JobNumber
)}`}
onClick={() =>
updateSortColumn(SORTABLE_COLUMN.JobNumber)
}
style={{
pointerEvents: isLoading ? "none" : "auto",
}}
>
{t(
getTranslationID("dictationPage.label.jobNumber")
)}
</a>
</th>
)}
{displayColumn.Status && (
<th className={styles.clm2}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.hasSort} ${sortIconClass(
sortableParamName,
sortDirection,
SORTABLE_COLUMN.Status
)}`}
onClick={() =>
updateSortColumn(SORTABLE_COLUMN.Status)
}
style={{
pointerEvents: isLoading ? "none" : "auto",
}}
>
{t(getTranslationID("dictationPage.label.status"))}
</a>
</th>
)}
{displayColumn.Priority && (
<th className={styles.clm3}>Priority</th>
)}
{displayColumn.Encryption && (
<th className={styles.clm4}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.hasSort} ${sortIconClass(
sortableParamName,
sortDirection,
SORTABLE_COLUMN.Encryption
)}`}
onClick={() =>
updateSortColumn(SORTABLE_COLUMN.Encryption)
}
style={{
pointerEvents: isLoading ? "none" : "auto",
}}
>
{t(
getTranslationID("dictationPage.label.encryption")
)}
</a>
</th>
)}
{displayColumn.AuthorId && (
<th className={styles.clm5}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.hasSort} ${sortIconClass(
sortableParamName,
sortDirection,
SORTABLE_COLUMN.AuthorId
)}`}
onClick={() =>
updateSortColumn(SORTABLE_COLUMN.AuthorId)
}
style={{
pointerEvents: isLoading ? "none" : "auto",
}}
>
{t(
getTranslationID("dictationPage.label.authorId")
)}
</a>
</th>
)}
{displayColumn.WorkType && (
<th className={styles.clm6}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.hasSort} ${sortIconClass(
sortableParamName,
sortDirection,
SORTABLE_COLUMN.WorkType
)}`}
onClick={() =>
updateSortColumn(SORTABLE_COLUMN.WorkType)
}
style={{
pointerEvents: isLoading ? "none" : "auto",
}}
>
{t(
getTranslationID("dictationPage.label.workType")
)}
</a>
</th>
)}
{displayColumn.FileName && (
<th className={styles.clm7}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.hasSort} ${sortIconClass(
sortableParamName,
sortDirection,
SORTABLE_COLUMN.FileName
)}`}
onClick={() =>
updateSortColumn(SORTABLE_COLUMN.FileName)
}
style={{
pointerEvents: isLoading ? "none" : "auto",
}}
>
{t(
getTranslationID("dictationPage.label.fileName")
)}
</a>
</th>
)}
{displayColumn.FileLength && (
<th className={styles.clm8}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.hasSort} ${sortIconClass(
sortableParamName,
sortDirection,
SORTABLE_COLUMN.FileLength
)}`}
onClick={() =>
updateSortColumn(SORTABLE_COLUMN.FileLength)
}
style={{
pointerEvents: isLoading ? "none" : "auto",
}}
>
{t(
getTranslationID("dictationPage.label.fileLength")
)}
</a>
</th>
)}
{displayColumn.FileSize && (
<th className={styles.clm9}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.hasSort} ${sortIconClass(
sortableParamName,
sortDirection,
SORTABLE_COLUMN.FileSize
)}`}
onClick={() =>
updateSortColumn(SORTABLE_COLUMN.FileSize)
}
style={{
pointerEvents: isLoading ? "none" : "auto",
}}
>
{t(
getTranslationID("dictationPage.label.fileSize")
)}
</a>
</th>
)}
{displayColumn.RecordingStartedDate && (
<th className={styles.clm10}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.hasSort} ${sortIconClass(
sortableParamName,
sortDirection,
SORTABLE_COLUMN.RecordingStartedDate
)}`}
onClick={() =>
updateSortColumn(
SORTABLE_COLUMN.RecordingStartedDate
)
}
style={{
pointerEvents: isLoading ? "none" : "auto",
}}
>
{t(
getTranslationID(
"dictationPage.label.recordingStartedDate"
)
)}
</a>
</th>
)}
{displayColumn.RecordingFinishedDate && (
<th className={styles.clm11}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.hasSort} ${sortIconClass(
sortableParamName,
sortDirection,
SORTABLE_COLUMN.RecordingFinishedDate
)}`}
onClick={() =>
updateSortColumn(
SORTABLE_COLUMN.RecordingFinishedDate
)
}
style={{
pointerEvents: isLoading ? "none" : "auto",
}}
>
{t(
getTranslationID(
"dictationPage.label.recordingFinishedDate"
)
)}
</a>
</th>
)}
{displayColumn.UploadDate && (
<th className={styles.clm12}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.hasSort} ${sortIconClass(
sortableParamName,
sortDirection,
SORTABLE_COLUMN.UploadDate
)}`}
onClick={() =>
updateSortColumn(SORTABLE_COLUMN.UploadDate)
}
style={{
pointerEvents: isLoading ? "none" : "auto",
}}
>
{t(
getTranslationID("dictationPage.label.uploadDate")
)}
</a>
</th>
)}
{displayColumn.TranscriptionStartedDate && (
<th className={styles.clm13}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.hasSort} ${sortIconClass(
sortableParamName,
sortDirection,
SORTABLE_COLUMN.TranscriptionStartedDate
)}`}
onClick={() =>
updateSortColumn(
SORTABLE_COLUMN.TranscriptionStartedDate
)
}
style={{
pointerEvents: isLoading ? "none" : "auto",
}}
>
{t(
getTranslationID(
"dictationPage.label.transcriptionStartedDate"
)
)}
</a>
</th>
)}
{displayColumn.TranscriptionFinishedDate && (
<th className={styles.clm14}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${styles.hasSort} ${sortIconClass(
sortableParamName,
sortDirection,
SORTABLE_COLUMN.TranscriptionFinishedDate
)}`}
onClick={() =>
updateSortColumn(
SORTABLE_COLUMN.TranscriptionFinishedDate
)
}
style={{
pointerEvents: isLoading ? "none" : "auto",
}}
>
{t(
getTranslationID(
"dictationPage.label.transcriptionFinishedDate"
)
)}
</a>
</th>
)}
{displayColumn.Transcriptionist && (
<th className={styles.clm15}>
{t(
getTranslationID(
"dictationPage.label.transcriptionist"
)
)}
</th>
)}
{displayColumn.Comment && (
<th className={styles.clm16}>
{t(getTranslationID("dictationPage.label.comment"))}
</th>
)}
{displayColumn.OptionItem1 && (
<th className={styles.op1}>
{t(
getTranslationID("dictationPage.label.optionItem1")
)}
</th>
)}
{displayColumn.OptionItem2 && (
<th className={styles.op2}>
{t(
getTranslationID("dictationPage.label.optionItem2")
)}
</th>
)}
{displayColumn.OptionItem3 && (
<th className={styles.op3}>
{t(
getTranslationID("dictationPage.label.optionItem3")
)}
</th>
)}
{displayColumn.OptionItem4 && (
<th className={styles.op4}>
{t(
getTranslationID("dictationPage.label.optionItem4")
)}
</th>
)}
{displayColumn.OptionItem5 && (
<th className={styles.op5}>
{t(
getTranslationID("dictationPage.label.optionItem5")
)}
</th>
)}
{displayColumn.OptionItem6 && (
<th className={styles.op6}>
{t(
getTranslationID("dictationPage.label.optionItem6")
)}
</th>
)}
{displayColumn.OptionItem7 && (
<th className={styles.op7}>
{t(
getTranslationID("dictationPage.label.optionItem7")
)}
</th>
)}
{displayColumn.OptionItem8 && (
<th className={styles.op8}>
{t(
getTranslationID("dictationPage.label.optionItem8")
)}
</th>
)}
{displayColumn.OptionItem9 && (
<th className={styles.op9}>
{t(
getTranslationID("dictationPage.label.optionItem9")
)}
</th>
)}
{displayColumn.OptionItem10 && (
<th className={styles.op10}>
{t(
getTranslationID("dictationPage.label.optionItem10")
)}
</th>
)}
</tr>
{(isChangeTranscriptionistPopupOpen || !isLoading) &&
tasks.length !== 0 &&
tasks.map((x) => (
<tr key={x.audioFileId}>
<td className={styles.clm0}>
<ul className={styles.menuInTable}>
<li>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={isNone ? styles.isDisable : ""}
onClick={() => onPlayBack(x.audioFileId)}
>
{t(
getTranslationID(
"dictationPage.label.playback"
)
)}
</a>
</li>
<li>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a onClick={() => onClickFileProperty(x)}>
{t(
getTranslationID(
"dictationPage.label.fileProperty"
)
)}
</a>
</li>
<li>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={
x.status !== STATUS.UPLOADED ||
!(isAdmin || isAuthor)
? styles.isDisable
: ""
}
onClick={() => {
onChangeTranscriptionistPopupOpen(x);
}}
>
{t(
getTranslationID(
"dictationPage.label.changeTranscriptionist"
)
)}
</a>
</li>
<li>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={
(x.status === STATUS.INPROGRESS ||
x.status === STATUS.PENDING) &&
(isAdmin || isTypist)
? ""
: styles.isDisable
}
onClick={() => {
onCancel(x.audioFileId);
}}
>
{t(
getTranslationID(
"dictationPage.label.cancelDictation"
)
)}
</a>
</li>
<li>
{/* タスクのステータスがFinishedかつ、ログインユーザーがAdminかTypistの場合、Change status to Pendingボタンを活性化する */}
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={
x.status === STATUS.FINISHED &&
(isAdmin || isTypist)
? ""
: styles.isDisable
}
onClick={() => {
onReopen(x.audioFileId);
}}
>
{t(
getTranslationID(
"dictationPage.label.reopenDictation"
)
)}
</a>
</li>
<li>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
// タスクのステータスがInprogressまたはPending以外の場合、削除ボタンを活性化する
className={
isDeletableRole &&
x.status !== STATUS.INPROGRESS &&
x.status !== STATUS.PENDING
? ""
: styles.isDisable
}
onClick={() => onDeleteTask(x.audioFileId)}
>
{t(
getTranslationID(
"dictationPage.label.deleteDictation"
)
)}
</a>
</li>
</ul>
</td>
{displayColumn.JobNumber && (
<td className={styles.clm1}>{x.jobNumber}</td>
)}
{displayColumn.Status && (
<td className={styles.clm2}>
{(() => {
switch (x.status) {
case STATUS.UPLOADED:
return (
<img src={uploaded} alt="Uploaded" />
);
case STATUS.PENDING:
return <img src={pending} alt="Pending" />;
case STATUS.FINISHED:
return (
<img src={finished} alt="Finished" />
);
case STATUS.INPROGRESS:
return (
<img src={inprogress} alt="InProgress" />
);
default:
return <img src={backup} alt="Backup" />;
}
})()}
{x.status}
</td>
)}
{displayColumn.Priority && (
<td
className={styles.clm3}
style={{
color: x.priority === "01" ? "red" : undefined,
}}
>
{x.priority === "01"
? PRIORITY.HIGH
: PRIORITY.NORMAL}
</td>
)}
{displayColumn.Encryption && (
<td className={styles.clm4}>
{x.isEncrypted ? (
<img src={lock} alt="encrypted" />
) : (
<>-</>
)}
</td>
)}
{displayColumn.AuthorId && (
<td className={styles.clm5}>{x.authorId}</td>
)}
{displayColumn.WorkType && (
<td className={styles.clm6}>{x.workType}</td>
)}
{displayColumn.FileName && (
<td className={styles.clm7}>{x.fileName}</td>
)}
{displayColumn.FileLength && (
<td className={styles.clm8}>{x.audioDuration}</td>
)}
{displayColumn.FileSize && (
<td className={styles.clm9}>{x.fileSize}</td>
)}
{displayColumn.RecordingStartedDate && (
<td className={styles.clm10}>
{x.audioCreatedDate}
</td>
)}
{displayColumn.RecordingFinishedDate && (
<td className={styles.clm11}>
{x.audioFinishedDate}
</td>
)}
{displayColumn.UploadDate && (
<td className={styles.clm12}>
{x.audioUploadedDate}
</td>
)}
{displayColumn.TranscriptionStartedDate && (
<td className={styles.clm13}>
{x.transcriptionStartedDate}
</td>
)}
{displayColumn.TranscriptionFinishedDate && (
<td className={styles.clm14}>
{x.transcriptionFinishedDate}
</td>
)}
{displayColumn.Transcriptionist && (
<td
className={`${styles.txWsline} ${styles.clm15}`}
>
{x.assignees.map((a, i) => (
<>
{a.typistName}
{i !== x.assignees.length - 1 && <br />}
</>
))}
</td>
)}
{displayColumn.Comment && (
<td className={styles.clm16}>{x.comment}</td>
)}
{displayColumn.OptionItem1 && (
<td className={styles.op1}>
{x.optionItemList[0].optionItemValue}
</td>
)}
{displayColumn.OptionItem2 && (
<td className={styles.op2}>
{x.optionItemList[1].optionItemValue}
</td>
)}
{displayColumn.OptionItem3 && (
<td className={styles.op3}>
{x.optionItemList[2].optionItemValue}
</td>
)}
{displayColumn.OptionItem4 && (
<td className={styles.op4}>
{x.optionItemList[3].optionItemValue}
</td>
)}
{displayColumn.OptionItem5 && (
<td className={styles.op5}>
{x.optionItemList[4].optionItemValue}
</td>
)}
{displayColumn.OptionItem6 && (
<td className={styles.op6}>
{x.optionItemList[5].optionItemValue}
</td>
)}
{displayColumn.OptionItem7 && (
<td className={styles.op7}>
{x.optionItemList[6].optionItemValue}
</td>
)}
{displayColumn.OptionItem8 && (
<td className={styles.op8}>
{x.optionItemList[7].optionItemValue}
</td>
)}
{displayColumn.OptionItem9 && (
<td className={styles.op9}>
{x.optionItemList[8].optionItemValue}
</td>
)}
{displayColumn.OptionItem10 && (
<td className={styles.op10}>
{x.optionItemList[9].optionItemValue}
</td>
)}
</tr>
))}
</table>
{(isChangeTranscriptionistPopupOpen || !isLoading) &&
tasks.length === 0 && (
<p style={{ margin: "10px", textAlign: "center" }}>
{t(getTranslationID("common.message.listEmpty"))}
</p>
)}
</div>
{isLoading && (
<img
style={{ position: "sticky" }}
src={progress_activit}
className={styles.icLoading}
alt="Loading"
/>
)}
{/** pagenation */}
<div className={styles.pagenation}>
<nav className={styles.pagenationNav}>
<span className={styles.pagenationTotal}>{`${total} ${t(
getTranslationID("dictationPage.label.title")
)}`}</span>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${!isLoading && currentPage !== 1 ? styles.isActive : ""
}`}
onClick={getFirstPage}
>
«
</a>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${!isLoading && currentPage !== 1 ? styles.isActive : ""
}`}
onClick={getPrevPage}
>
</a>
{`${currentPage} of ${totalPage}`}
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${!isLoading && currentPage < totalPage
? styles.isActive
: ""
}`}
onClick={getNextPage}
>
</a>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
className={`${!isLoading && currentPage < totalPage
? styles.isActive
: ""
}`}
onClick={getLastPage}
>
»
</a>
</nav>
</div>
<ul className={`${styles.menuAction} ${styles.alignRight}`}>
<li className={styles.alignLeft}>
<a
// TODO: 将来的に正式なURLに変更する
href="https://download.omsystem.com/pages/odms_download/odms_cloud_desktop/en/"
className={`${styles.menuLink} ${styles.isActive}`}
target="_blank"
rel="noreferrer"
>
{t(getTranslationID("dictationPage.label.applications"))}
<img
src={open_in_new}
alt=""
className={styles.menuIcon}
/>
</a>
</li>
<li>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
<a
onClick={onClickBackup}
className={`${styles.menuLink} ${isAdmin ? styles.isActive : ""
}`}
>
<img src={download} alt="" className={styles.menuIcon} />
{t(getTranslationID("dictationPage.label.fileBackup"))}
</a>
</li>
</ul>
</div>
</section>
</div>
</main>
<Footer />
</div>
</>
);
};
export default DictationPage;