Merged PR 462: ワークフロー追加ポップアップ実装

## 概要
[Task2740: ワークフロー追加ポップアップ実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2740)

- ワークフロー追加Popupを実装
- 多言語対応

## レビューポイント
- タイピスト・タイピストグループの変更処理
  - 選択・除外で処理を分けたが一緒にしたほうが良い?
- 各パラメータの変更処理
  - 値のチェックは足りているか

## 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/Task2740?csf=1&web=1&e=UHGFtv

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
This commit is contained in:
saito.k 2023-10-10 00:57:11 +00:00
parent aef6a35a7d
commit 7682c41ba5
11 changed files with 861 additions and 145 deletions

View File

@ -55,4 +55,5 @@ export const errorCodes = [
"E011001", // ワークタイプ重複エラー
"E011002", // ワークタイプ登録上限超過エラー
"E011003", // ワークタイプ不在エラー
"E013001", // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
] as const;

View File

@ -1,9 +1,22 @@
import { createAsyncThunk } from "@reduxjs/toolkit";
import { Configuration, GetWorkflowsResponse, WorkflowsApi } from "api";
import {
AccountsApi,
Author,
Configuration,
GetWorkflowsResponse,
TemplateFile,
TemplatesApi,
Typist,
TypistGroup,
WorkflowTypist,
WorkflowsApi,
Worktype,
} from "api";
import type { RootState } from "app/store";
import { ErrorObject, createErrorObject } from "common/errors";
import { openSnackbar } from "features/ui/uiSlice";
import { getTranslationID } from "translation";
import { WorkflowRelations } from "./state";
export const listWorkflowAsync = createAsyncThunk<
GetWorkflowsResponse,
@ -40,3 +53,161 @@ export const listWorkflowAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error });
}
});
export const createWorkflowAsync = createAsyncThunk<
{
/* Empty Object */
},
void,
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("workflow/createWorkflowAsync", async (args, thunkApi) => {
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration, accessToken } = state.auth;
const config = new Configuration(configuration);
const workflowsApi = new WorkflowsApi(config);
const { selectedAssignees, authorId, templateId, worktypeId } =
state.workflow.apps;
try {
if (authorId === undefined) {
throw new Error("authorId is not found");
}
// 選択されたタイピストを取得し、リクエスト用の型に変換する
const typists = selectedAssignees.map(
(item): WorkflowTypist => ({
typistId: item.typistUserId,
typistGroupId: item.typistGroupId,
})
);
await workflowsApi.createWorkflows(
{
authorId,
typists,
templateId,
worktypeId,
},
{
headers: { authorization: `Bearer ${accessToken}` },
}
);
thunkApi.dispatch(
openSnackbar({
level: "info",
message: getTranslationID("common.message.success"),
})
);
return {};
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
const { code, statusCode } = error;
// AuthorIDとWorktypeIDが一致するものが既に存在する場合
if (code === "E013001") {
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID(
"workflowPage.message.workflowConflictError"
),
})
);
return thunkApi.rejectWithValue({ error });
}
// パラメータが存在しない場合
if (statusCode === 400) {
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID("workflowPage.message.saveFailedError"),
})
);
return thunkApi.rejectWithValue({ error });
}
// その他のエラー
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID("common.message.internalServerError"),
})
);
return thunkApi.rejectWithValue({ error });
}
});
export const getworkflowRelationsAsync = createAsyncThunk<
{
authors: Author[];
typists: Typist[];
typistGroups: TypistGroup[];
templates: TemplateFile[];
worktypes: Worktype[];
},
void,
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("workflow/getworkflowRelationsAsync", async (args, thunkApi) => {
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration, accessToken } = state.auth;
const config = new Configuration(configuration);
const accountsApi = new AccountsApi(config);
const templatesApi = new TemplatesApi(config);
try {
const { authors } = (
await accountsApi.getAuthors({
headers: { authorization: `Bearer ${accessToken}` },
})
).data;
const { typists } = (
await accountsApi.getTypists({
headers: { authorization: `Bearer ${accessToken}` },
})
).data;
const { typistGroups } = (
await accountsApi.getTypistGroups({
headers: { authorization: `Bearer ${accessToken}` },
})
).data;
const { templates } = (
await templatesApi.getTemplates({
headers: { authorization: `Bearer ${accessToken}` },
})
).data;
const { worktypes } = (
await accountsApi.getWorktypes({
headers: { authorization: `Bearer ${accessToken}` },
})
).data;
return {
authors,
typists,
typistGroups,
templates,
worktypes,
};
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
thunkApi.dispatch(
openSnackbar({
level: "error",
message: getTranslationID("common.message.internalServerError"),
})
);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -1,3 +1,4 @@
import { Assignee } from "api";
import { RootState } from "app/store";
export const selectWorkflows = (state: RootState) =>
@ -5,3 +6,47 @@ export const selectWorkflows = (state: RootState) =>
export const selectIsLoading = (state: RootState) =>
state.workflow.apps.isLoading;
export const selectWorkflowRelations = (state: RootState) =>
state.workflow.domain.workflowRelations;
export const selectWorkflowAssinee = (state: RootState) => {
// 選択されたassigneeを取得
const { selectedAssignees } = state.workflow.apps;
// すべてのassigneeを取得
const assignees = state.workflow.domain.workflowRelations?.assignees ?? [];
// assigneeが選択されているかどうかを判定する
const isAssigneeSelected = (assignee: Assignee) =>
selectedAssignees.some(
(sa) =>
sa.typistUserId === assignee.typistUserId &&
sa.typistGroupId === assignee.typistGroupId
);
// 未選択のassigneeを取得する
const poolAssignees = assignees.filter(
(assignee) => !isAssigneeSelected(assignee)
);
// selectedAssigneesとpoolAssigneesをtypistNameでソートして返す
return {
selectedAssignees: [...selectedAssignees].sort((a, b) =>
a.typistName.localeCompare(b.typistName)
),
poolAssignees: poolAssignees.sort((a, b) =>
a.typistName.localeCompare(b.typistName)
),
};
};
export const selectIsAddLoading = (state: RootState) =>
state.workflow.apps.isAddLoading;
export const selectWorkflowError = (state: RootState) => {
// authorIdがundefinedの場合はエラーを返す
const hasAuthorIdEmptyError = state.workflow.apps.authorId === undefined;
// workflowAssineeのselectedが空の場合はエラーを返す
const hasSelectedWorkflowAssineeEmptyError =
state.workflow.apps.selectedAssignees.length === 0;
return {
hasAuthorIdEmptyError,
hasSelectedWorkflowAssineeEmptyError,
};
};

View File

@ -1,4 +1,4 @@
import { Workflow } from "api";
import { Assignee, Author, TemplateFile, Workflow, Worktype } from "api";
export interface WorkflowState {
apps: Apps;
@ -7,8 +7,21 @@ export interface WorkflowState {
export interface Apps {
isLoading: boolean;
isAddLoading: boolean;
selectedAssignees: Assignee[];
authorId?: number;
worktypeId?: number;
templateId?: number;
}
export interface Domain {
workflows?: Workflow[];
workflowRelations?: WorkflowRelations;
}
export interface WorkflowRelations {
authors: Author[];
assignees: Assignee[];
templates: TemplateFile[];
worktypes: Worktype[];
}

View File

@ -1,10 +1,17 @@
import { createSlice } from "@reduxjs/toolkit";
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { Assignee } from "api";
import {
createWorkflowAsync,
getworkflowRelationsAsync,
listWorkflowAsync,
} from "./operations";
import { WorkflowState } from "./state";
import { listWorkflowAsync } from "./operations";
const initialState: WorkflowState = {
apps: {
isLoading: false,
isAddLoading: false,
selectedAssignees: [],
},
domain: {},
};
@ -12,7 +19,55 @@ const initialState: WorkflowState = {
export const workflowSlice = createSlice({
name: "workflow",
initialState,
reducers: {},
reducers: {
clearWorkflow: (state) => {
state.apps.selectedAssignees = [];
state.apps.authorId = undefined;
state.apps.worktypeId = undefined;
state.apps.templateId = undefined;
state.domain.workflowRelations = undefined;
},
addAssignee: (state, action: PayloadAction<{ assignee: Assignee }>) => {
const { assignee } = action.payload;
const { selectedAssignees } = state.apps;
// assigneeがselectedAssigneesに存在するか確認する
const isDuplicate = selectedAssignees.some(
(x) =>
x.typistUserId === assignee.typistUserId &&
x.typistGroupId === assignee.typistGroupId
);
// 重複していなければ追加する
if (!isDuplicate) {
const newSelectedAssignees = [...selectedAssignees, assignee];
// stateに保存する
state.apps.selectedAssignees = newSelectedAssignees;
}
},
removeAssignee: (state, action: PayloadAction<{ assignee: Assignee }>) => {
const { assignee } = action.payload;
const { selectedAssignees } = state.apps;
// selectedAssigneeの要素からassigneeを削除する
state.apps.selectedAssignees = selectedAssignees.filter(
(x) =>
x.typistUserId !== assignee.typistUserId ||
x.typistGroupId !== assignee.typistGroupId
);
},
changeAuthor: (state, action: PayloadAction<{ authorId: number }>) => {
const { authorId } = action.payload;
state.apps.authorId = authorId;
},
changeWorktype: (state, action: PayloadAction<{ worktypeId?: number }>) => {
const { worktypeId } = action.payload;
state.apps.worktypeId = worktypeId;
},
changeTemplate: (state, action: PayloadAction<{ templateId?: number }>) => {
const { templateId } = action.payload;
state.apps.templateId = templateId;
},
},
extraReducers: (builder) => {
builder.addCase(listWorkflowAsync.pending, (state) => {
state.apps.isLoading = true;
@ -26,7 +81,62 @@ export const workflowSlice = createSlice({
builder.addCase(listWorkflowAsync.rejected, (state) => {
state.apps.isLoading = false;
});
builder.addCase(getworkflowRelationsAsync.pending, (state) => {
state.apps.isAddLoading = true;
});
builder.addCase(getworkflowRelationsAsync.fulfilled, (state, action) => {
const { authors, typistGroups, typists, templates, worktypes } =
action.payload;
// 取得したtypistsとtypistGroupsを型変換
const assineeTypists = typists.map(
(typist): Assignee => ({
typistUserId: typist.id,
typistGroupId: undefined,
typistName: typist.name,
})
);
const assineeTypistGroups = typistGroups.map(
(typistGroup): Assignee => ({
typistUserId: undefined,
typistGroupId: typistGroup.id,
typistName: typistGroup.name,
})
);
// 取得したtypistsとtypistGroupsを結合
const assinees = [...assineeTypists, ...assineeTypistGroups];
// storeに保存
state.domain.workflowRelations = {
authors,
assignees: assinees,
templates,
worktypes,
};
state.apps.isAddLoading = false;
});
builder.addCase(getworkflowRelationsAsync.rejected, (state) => {
state.apps.isAddLoading = false;
});
builder.addCase(createWorkflowAsync.pending, (state) => {
state.apps.isAddLoading = true;
});
builder.addCase(createWorkflowAsync.fulfilled, (state) => {
state.apps.isAddLoading = false;
});
builder.addCase(createWorkflowAsync.rejected, (state) => {
state.apps.isAddLoading = false;
});
},
});
export const {
addAssignee,
removeAssignee,
changeAuthor,
changeWorktype,
changeTemplate,
clearWorkflow,
} = workflowSlice.actions;
export default workflowSlice.reducer;

View File

@ -0,0 +1,293 @@
import React, { useCallback, useEffect, useState } from "react";
import { AppDispatch } from "app/store";
import progress_activit from "assets/images/progress_activit.svg";
import {
addAssignee,
removeAssignee,
changeAuthor,
changeTemplate,
changeWorktype,
clearWorkflow,
selectIsAddLoading,
selectWorkflowAssinee,
selectWorkflowError,
selectWorkflowRelations,
} from "features/workflow";
import {
createWorkflowAsync,
getworkflowRelationsAsync,
listWorkflowAsync,
} from "features/workflow/operations";
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
import styles from "styles/app.module.scss";
import { getTranslationID } from "translation";
import close from "../../assets/images/close.svg";
interface AddWorkflowPopupProps {
onClose: () => void;
}
export const AddWorkflowPopup: React.FC<AddWorkflowPopupProps> = (
props
): JSX.Element => {
const { onClose } = props;
const dispatch: AppDispatch = useDispatch();
const [t] = useTranslation();
// 保存ボタンを押したかどうか
const [isPushAddButton, setIsPushAddButton] = useState<boolean>(false);
const workflowRelations = useSelector(selectWorkflowRelations);
const { poolAssignees, selectedAssignees } = useSelector(
selectWorkflowAssinee
);
const isLoading = useSelector(selectIsAddLoading);
const { hasAuthorIdEmptyError, hasSelectedWorkflowAssineeEmptyError } =
useSelector(selectWorkflowError);
useEffect(() => {
dispatch(getworkflowRelationsAsync());
// ポップアップのアンマウント時に初期化を行う
return () => {
dispatch(clearWorkflow());
setIsPushAddButton(false);
};
}, [dispatch]);
const changeWorktypeId = useCallback(
(target: string) => {
// 空文字の場合はundefinedをdispatchする
if (target === "") {
dispatch(changeWorktype({ worktypeId: undefined }));
} else if (!Number.isNaN(Number(target))) {
dispatch(changeWorktype({ worktypeId: Number(target) }));
}
},
[dispatch]
);
const changeTemplateId = useCallback(
(target: string) => {
// 空文字の場合はundefinedをdispatchする
if (target === "") {
dispatch(changeTemplate({ templateId: undefined }));
} else if (!Number.isNaN(Number(target))) {
dispatch(changeTemplate({ templateId: Number(target) }));
}
},
[dispatch]
);
const changeAuthorId = useCallback(
(target: string) => {
if (!Number.isNaN(target)) {
dispatch(changeAuthor({ authorId: Number(target) }));
}
},
[dispatch]
);
// 追加ボタン押下時の処理
const handleAdd = useCallback(async () => {
setIsPushAddButton(true);
// エラーチェック
if (hasAuthorIdEmptyError || hasSelectedWorkflowAssineeEmptyError) {
return;
}
const { meta } = await dispatch(createWorkflowAsync());
if (meta.requestStatus === "fulfilled") {
onClose();
dispatch(listWorkflowAsync());
}
}, [
dispatch,
hasAuthorIdEmptyError,
hasSelectedWorkflowAssineeEmptyError,
onClose,
]);
return (
<div className={`${styles.modal} ${styles.isShow}`}>
<div className={styles.modalBox}>
<p className={styles.modalTitle}>
{t(getTranslationID("worktypeIdSetting.label.addWorktypeId"))}
{/* 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={onClose}
/>
</p>
<form className={styles.form}>
<dl className={`${styles.formList} ${styles.hasbg}`}>
<dt className={styles.formTitle} />
<dt>{t(getTranslationID("workflowPage.label.authorID"))}</dt>
<dd>
<select
className={styles.formInput}
onChange={(e) => {
changeAuthorId(e.target.value);
}}
>
<option value="" hidden>
{`-- ${t(
getTranslationID("workflowPage.label.selectAuthor")
)} --`}
</option>
{workflowRelations?.authors.map((author) => (
<option key={author.authorId} value={author.id}>
{author.authorId}
</option>
))}
</select>
{isPushAddButton && hasAuthorIdEmptyError && (
<span className={styles.formError}>
{t(getTranslationID("workflowPage.message.inputEmptyError"))}
</span>
)}
</dd>
<dt className={styles.overLine}>
{t(getTranslationID("workflowPage.label.worktypeOptional"))}
</dt>
<dd>
<select
className={styles.formInput}
onChange={(e) => {
changeWorktypeId(e.target.value);
}}
>
<option value="" hidden>
{`-- ${t(
getTranslationID("workflowPage.label.selectWorktypeId")
)} --`}
</option>
<option value="">
{`-- ${t(getTranslationID("common.label.notSelected"))} --`}
</option>
{workflowRelations?.worktypes.map((worktype) => (
<option key={worktype.id} value={worktype.id}>
{worktype.worktypeId}
</option>
))}
</select>
</dd>
<dt className={styles.formTitle}>
{t(getTranslationID("typistGroupSetting.label.transcriptionist"))}
</dt>
<dd className={`${styles.formChange} ${styles.last}`}>
<ul className={styles.chooseMember}>
<li className={styles.changeTitle}>
{t(getTranslationID("workflowPage.label.selected"))}
</li>
{selectedAssignees?.map((x) => {
const key = `${x.typistName}_${
x.typistUserId ?? x.typistGroupId
}`;
return (
<li key={key}>
<input
type="checkbox"
className={styles.formCheck}
value={x.typistName}
id={key}
checked
onClick={() => {
dispatch(removeAssignee({ assignee: x }));
}}
/>
<label htmlFor={key} title="Remove">
{x.typistName}
</label>
</li>
);
})}
</ul>
<p />
<ul className={styles.holdMember}>
<li className={styles.changeTitle}>
{t(getTranslationID("workflowPage.label.pool"))}
</li>
{poolAssignees?.map((x) => {
const key = `${x.typistName}_${
x.typistUserId ?? x.typistGroupId
}`;
return (
<li key={key}>
<input
type="checkbox"
className={styles.formCheck}
value={x.typistName}
id={key}
onClick={() => dispatch(addAssignee({ assignee: x }))}
/>
<label htmlFor={key} title="Add">
{x.typistName}
</label>
</li>
);
})}
</ul>
{isPushAddButton && hasSelectedWorkflowAssineeEmptyError && (
<span
className={styles.formError}
style={{ margin: "0px 30px 0px 30px" }}
>
{t(
getTranslationID(
"workflowPage.message.selectedTypistEmptyError"
)
)}
</span>
)}
</dd>
<dt className={styles.overLine}>
{t(getTranslationID("workflowPage.label.templateOptional"))}
</dt>
<dd className={styles.last}>
<select
className={styles.formInput}
onChange={(e) => {
changeTemplateId(e.target.value);
}}
>
<option value="" hidden>
{`-- ${t(
getTranslationID("workflowPage.label.selectTemplate")
)} --`}
</option>
<option value="">
{`-- ${t(getTranslationID("common.label.notSelected"))} --`}
</option>
{workflowRelations?.templates.map((template) => (
<option
key={`${template.name}_${template.id}`}
value={template.id}
>
{template.name}
</option>
))}
</select>
</dd>
<dd className={`${styles.full} ${styles.alignCenter}`}>
<input
type="button"
value={t(getTranslationID("common.label.save"))}
className={`${styles.formSubmit} ${styles.marginBtm1} ${
!isLoading ? styles.isActive : ""
}`}
onClick={handleAdd}
/>
{isLoading && (
<img
src={progress_activit}
className={styles.icLoading}
alt="Loading"
/>
)}
</dd>
</dl>
</form>
</div>
</div>
);
};

View File

@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import Header from "components/header";
import Footer from "components/footer";
import styles from "styles/app.module.scss";
@ -14,10 +14,13 @@ import { listWorkflowAsync } from "features/workflow/operations";
import { selectIsLoading, selectWorkflows } from "features/workflow";
import progress_activit from "assets/images/progress_activit.svg";
import { getTranslationID } from "translation";
import { AddWorkflowPopup } from "./addworkflowPopup";
const WorkflowPage: React.FC = (): JSX.Element => {
const dispatch: AppDispatch = useDispatch();
const [t] = useTranslation();
// 追加Popupの表示制御
const [isShowAddPopup, setIsShowAddPopup] = useState<boolean>(false);
const workflows = useSelector(selectWorkflows);
const isLoading = useSelector(selectIsLoading);
@ -25,6 +28,14 @@ const WorkflowPage: React.FC = (): JSX.Element => {
dispatch(listWorkflowAsync());
}, [dispatch]);
return (
<>
{isShowAddPopup && (
<AddWorkflowPopup
onClose={() => {
setIsShowAddPopup(false);
}}
/>
)}
<div className={styles.wrap}>
<Header userName="XXXXXX" />
<UpdateTokenTimer />
@ -39,7 +50,13 @@ const WorkflowPage: React.FC = (): JSX.Element => {
<div>
<ul className={`${styles.menuAction} ${styles.alignRight}`}>
<li className={styles.floatLeft}>
<a className={`${styles.menuLink} ${styles.isActive}`}>
{/* 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={ruleAddImg}
alt="addRoutingRule"
@ -48,7 +65,6 @@ const WorkflowPage: React.FC = (): JSX.Element => {
{t(getTranslationID("workflowPage.label.addRoutingRule"))}
</a>
</li>
<li>
<a
href="/workflow/template"
@ -59,7 +75,9 @@ const WorkflowPage: React.FC = (): JSX.Element => {
alt="templateSetting"
className={styles.menuIcon}
/>
{t(getTranslationID("workflowPage.label.templateSetting"))}
{t(
getTranslationID("workflowPage.label.templateSetting")
)}
</a>
</li>
<li>
@ -88,7 +106,9 @@ const WorkflowPage: React.FC = (): JSX.Element => {
className={styles.menuIcon}
/>
{t(
getTranslationID("workflowPage.label.typistGroupSetting")
getTranslationID(
"workflowPage.label.typistGroupSetting"
)
)}
</a>
</li>
@ -96,12 +116,20 @@ const WorkflowPage: React.FC = (): JSX.Element => {
<table className={`${styles.table} ${styles.workflow}`}>
<tr className={styles.tableHeader}>
<th className={styles.clm0}>{/** empty th */}</th>
<th>{t(getTranslationID("workflowPage.label.authorID"))}</th>
<th>{t(getTranslationID("workflowPage.label.worktype"))}</th>
<th>
{t(getTranslationID("workflowPage.label.transcriptionist"))}
{t(getTranslationID("workflowPage.label.authorID"))}
</th>
<th>
{t(getTranslationID("workflowPage.label.worktype"))}
</th>
<th>
{t(
getTranslationID("workflowPage.label.transcriptionist")
)}
</th>
<th>
{t(getTranslationID("workflowPage.label.template"))}
</th>
<th>{t(getTranslationID("workflowPage.label.template"))}</th>
</tr>
{workflows?.map((workflow) => (
<tr key={workflow.id}>
@ -109,7 +137,9 @@ const WorkflowPage: React.FC = (): JSX.Element => {
<ul className={styles.menuInTable}>
<li>
<a href="">
{t(getTranslationID("workflowPage.label.editRule"))}
{t(
getTranslationID("workflowPage.label.editRule")
)}
</a>
</li>
<li>
@ -157,6 +187,7 @@ const WorkflowPage: React.FC = (): JSX.Element => {
</main>
<Footer />
</div>
</>
);
};

View File

@ -363,9 +363,22 @@
"typistGroupSetting": "(de)Transcriptionist Group Setting",
"authorID": "Autoren-ID",
"worktype": "Aufgabentypkennung",
"worktypeOptional": "(de)Worktype ID (Optional)",
"transcriptionist": "Transkriptionist",
"template": "(de)Template",
"editRule": "(de)Edit Rule"
"templateOptional": "(de)Template (Optional)",
"editRule": "(de)Edit Rule",
"selected": "Ausgewählter transkriptionist",
"pool": "Transkriptionsliste",
"selectAuthor": "(de)Select Author",
"selectWorktypeId": "(de)Select Worktype ID",
"selectTemplate": "(de)Select Template"
},
"message": {
"selectedTypistEmptyError": "(de)Transcriptionist,TranscriptionistGroupがいないルーティングルールは保存できません。ルーティング先を1つ以上選択してください。",
"workflowConflictError": "(de)指定したAuthorIDとWorktypeIDの組み合わせで既にルーティングルールが登録されています。他の組み合わせで登録してください。",
"inputEmptyError": "Pflichtfeld",
"saveFailedError": "(de)ルーティングルールの保存に失敗しました。画面を更新し、再度実行してください"
}
},
"typistGroupSetting": {

View File

@ -363,9 +363,22 @@
"typistGroupSetting": "Transcriptionist Group Setting",
"authorID": "Author ID",
"worktype": "Worktype ID",
"worktypeOptional": "Worktype ID (Optional)",
"transcriptionist": "Transcriptionist",
"template": "Template",
"editRule": "Edit Rule"
"templateOptional": "Template (Optional)",
"editRule": "Edit Rule",
"selected": "Selected Transcriptionist",
"pool": "Transcription List",
"selectAuthor": "Select Author",
"selectWorktypeId": "Select Worktype ID",
"selectTemplate": "Select Template"
},
"message": {
"selectedTypistEmptyError": "Transcriptionist,TranscriptionistGroupがいないルーティングルールは保存できません。ルーティング先を1つ以上選択してください。",
"workflowConflictError": "指定したAuthorIDとWorktypeIDの組み合わせで既にルーティングルールが登録されています。他の組み合わせで登録してください。",
"inputEmptyError": "Mandatory Field",
"saveFailedError": "ルーティングルールの保存に失敗しました。画面を更新し、再度実行してください"
}
},
"typistGroupSetting": {

View File

@ -363,9 +363,22 @@
"typistGroupSetting": "(es)Transcriptionist Group Setting",
"authorID": "ID de autor",
"worktype": "ID de tipo de trabajo",
"worktypeOptional": "(es)Worktype ID (Optional)",
"transcriptionist": "Transcriptor",
"template": "(es)Template",
"editRule": "(es)Edit Rule"
"templateOptional": "(es)Template (Optional)",
"editRule": "(es)Edit Rule",
"selected": "Transcriptor seleccionado",
"pool": "Lista de transcriptor",
"selectAuthor": "(es)Select Author",
"selectWorktypeId": "(es)Select Worktype ID",
"selectTemplate": "(es)Select Template"
},
"message": {
"selectedTypistEmptyError": "(es)Transcriptionist,TranscriptionistGroupがいないルーティングルールは保存できません。ルーティング先を1つ以上選択してください。",
"workflowConflictError": "(es)指定したAuthorIDとWorktypeIDの組み合わせで既にルーティングルールが登録されています。他の組み合わせで登録してください。",
"inputEmptyError": "Campo obligatorio",
"saveFailedError": "(es)ルーティングルールの保存に失敗しました。画面を更新し、再度実行してください"
}
},
"typistGroupSetting": {

View File

@ -363,9 +363,22 @@
"typistGroupSetting": "(fr)Transcriptionist Group Setting",
"authorID": "Identifiant Auteur",
"worktype": "Identifiant du Type de travail",
"worktypeOptional": "(fr)Worktype ID (Optional)",
"transcriptionist": "Transcriptionniste",
"template": "(fr)Template",
"editRule": "(fr)Edit Rule"
"templateOptional": "(fr)Template (Optional)",
"editRule": "(fr)Edit Rule",
"selected": "Transcriptionniste sélectionné",
"pool": "Liste de transcriptionniste",
"selectAuthor": "(fr)Select Author",
"selectWorktypeId": "(fr)Select Worktype ID",
"selectTemplate": "(fr)Select Template"
},
"message": {
"selectedTypistEmptyError": "(fr)Transcriptionist,TranscriptionistGroupがいないルーティングルールは保存できません。ルーティング先を1つ以上選択してください。",
"workflowConflictError": "(fr)指定したAuthorIDとWorktypeIDの組み合わせで既にルーティングルールが登録されています。他の組み合わせで登録してください。",
"inputEmptyError": "Champ obligatoire",
"saveFailedError": "(fr)ルーティングルールの保存に失敗しました。画面を更新し、再度実行してください"
}
},
"typistGroupSetting": {