Merged PR 343: 画面実装(TypistGroup設定画面)
## 概要 [Task2411: 画面実装(TypistGroup設定画面)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2411) - タイピストグループの設定画面を実装しました。 - タイピストグループの一覧を取得できるようにしています。 - return ボタンでワークフロー画面に遷移します。 - Edit、Add Groupは対象外です。 - ヘッダタブ表示のため、ヘッダを修正しています。 ## レビューポイント - パスの構成は問題ないか - ヘッダの対応に問題はないか ## UIの変更 - [Task2411](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/Task2411?csf=1&web=1&e=cxxeCf) ## 動作確認状況 - ローカルで確認
This commit is contained in:
parent
c85ecbfc90
commit
84e7deb52a
@ -13,6 +13,7 @@ import partnerLicense from "features/license/partnerLicense/partnerLicenseSlice"
|
||||
import dictation from "features/dictation/dictationSlice";
|
||||
import partner from "features/partner/partnerSlice";
|
||||
import licenseOrderHistory from "features/license/licenseOrderHistory/licenseOrderHistorySlice";
|
||||
import typistGroup from "features/workflow/typistGroup/typistGroupSlice";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
@ -30,6 +31,7 @@ export const store = configureStore({
|
||||
partnerLicense,
|
||||
dictation,
|
||||
partner,
|
||||
typistGroup,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
21
dictation_client/src/assets/images/group_add.svg
Normal file
21
dictation_client/src/assets/images/group_add.svg
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.7.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="レイヤー_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#282828;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M25.7,23.7c2.5,0.6,4.8,0.2,6.7-1.2s2.9-3.4,2.9-6.1s-1-4.8-2.9-6.1c-1.9-1.3-4.1-1.7-6.7-1.1
|
||||
c0.9,1.1,1.5,2.2,1.9,3.3s0.6,2.5,0.6,4s-0.2,2.8-0.6,3.9S26.6,22.6,25.7,23.7z"/>
|
||||
<path class="st0" d="M17.8,24c2.2,0,4-0.7,5.4-2.1s2.1-3.2,2.1-5.4s-0.7-4-2.1-5.4C21.8,9.6,20,9,17.8,9s-4,0.7-5.4,2.1
|
||||
s-2.1,3.2-2.1,5.4s0.7,4,2.1,5.4C13.8,23.2,15.5,24,17.8,24z M14.5,13.2c0.8-0.8,1.9-1.3,3.2-1.3s2.4,0.4,3.2,1.3
|
||||
c0.9,0.9,1.3,1.9,1.3,3.2s-0.4,2.4-1.3,3.2C20.1,20.5,19,21,17.8,21s-2.4-0.4-3.2-1.3c-0.9-0.8-1.3-1.9-1.3-3.2
|
||||
C13.2,15.1,13.7,14.1,14.5,13.2z"/>
|
||||
<path class="st0" d="M5,37v-1.7c0-0.5,0.1-1,0.4-1.5s0.7-0.8,1.2-1.1c2.4-1.1,4.3-1.8,5.9-2.2c1.6-0.4,3.3-0.5,5.2-0.5
|
||||
s3.7,0.2,5.2,0.5c0.4,0.1,0.9,0.2,1.4,0.4c0.7-0.8,1.5-1.6,2.4-2.3c-1.1-0.4-2.2-0.7-3.1-1c-1.9-0.5-3.8-0.7-5.9-0.7
|
||||
s-4,0.2-5.9,0.7s-4,1.2-6.4,2.3c-1,0.5-1.9,1.2-2.5,2.1C2.3,33,2,34,2,35.2V40h19.1c0-1,0.1-2,0.3-3H5z"/>
|
||||
<polygon class="st0" points="33.4,43.9 33.4,37.4 26.9,37.4 26.9,33.5 33.4,33.5 33.4,27 37.3,27 37.3,33.5 43.8,33.5 43.8,37.4
|
||||
37.3,37.4 37.3,43.9 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -28,6 +28,7 @@ export const ADMIN_ONLY_TABS = [
|
||||
HEADER_MENUS_USER,
|
||||
HEADER_MENUS_WORKFLOW,
|
||||
HEADER_MENUS_PARTNER,
|
||||
HEADER_MENUS_WORKFLOW,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@ -11,7 +11,15 @@ interface HeaderProps {
|
||||
const Header: React.FC<HeaderProps> = (props) => {
|
||||
const { userName } = props;
|
||||
const location = useLocation();
|
||||
return getHeader(location.pathname, userName);
|
||||
|
||||
const splitPaths = location.pathname.split("/");
|
||||
|
||||
let path = location.pathname;
|
||||
if (splitPaths.length >= 2) {
|
||||
path = `/${splitPaths[1]}`;
|
||||
}
|
||||
|
||||
return getHeader(path, userName);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
export * from "./typistGroupSlice";
|
||||
export * from "./state";
|
||||
export * from "./selectors";
|
||||
export * from "./operations";
|
||||
@ -0,0 +1,43 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import type { RootState } from "app/store";
|
||||
import { openSnackbar } from "features/ui/uiSlice";
|
||||
import { getTranslationID } from "translation";
|
||||
import { AccountsApi, GetTypistGroupsResponse } from "../../../api/api";
|
||||
import { Configuration } from "../../../api/configuration";
|
||||
import { ErrorObject, createErrorObject } from "../../../common/errors";
|
||||
|
||||
export const listTypistGroupsAsync = createAsyncThunk<
|
||||
GetTypistGroupsResponse,
|
||||
void,
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("dictations/listTypistGroupsAsync", 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);
|
||||
|
||||
try {
|
||||
const typistGroup = await accountsApi.getTypistGroups({
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
|
||||
return typistGroup.data;
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: getTranslationID("common.message.internalServerError"),
|
||||
})
|
||||
);
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,7 @@
|
||||
import { RootState } from "app/store";
|
||||
|
||||
export const selectTypistGroups = (state: RootState) =>
|
||||
state.typistGroup.domain.typistGroups;
|
||||
|
||||
export const selectIsLoading = (state: RootState) =>
|
||||
state.typistGroup.apps.isLoading;
|
||||
14
dictation_client/src/features/workflow/typistGroup/state.ts
Normal file
14
dictation_client/src/features/workflow/typistGroup/state.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { TypistGroup } from "../../../api/api";
|
||||
|
||||
export interface TypistGroupState {
|
||||
apps: Apps;
|
||||
domain: Domain;
|
||||
}
|
||||
|
||||
export interface Apps {
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export interface Domain {
|
||||
typistGroups: TypistGroup[];
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { TypistGroupState } from "./state";
|
||||
import { listTypistGroupsAsync } from "./operations";
|
||||
|
||||
const initialState: TypistGroupState = {
|
||||
apps: {
|
||||
isLoading: false,
|
||||
},
|
||||
domain: {
|
||||
typistGroups: [],
|
||||
},
|
||||
};
|
||||
|
||||
export const typistGroupSlice = createSlice({
|
||||
name: "typistGroup",
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(listTypistGroupsAsync.pending, (state) => {
|
||||
state.apps.isLoading = true;
|
||||
});
|
||||
builder.addCase(listTypistGroupsAsync.fulfilled, (state, action) => {
|
||||
state.domain.typistGroups = action.payload.typistGroups;
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
builder.addCase(listTypistGroupsAsync.rejected, (state) => {
|
||||
state.apps.isLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default typistGroupSlice.reducer;
|
||||
@ -1,18 +1,118 @@
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import Header from "components/header";
|
||||
import Footer from "components/footer";
|
||||
import styles from "styles/app.module.scss";
|
||||
import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
|
||||
import progress_activit from "assets/images/progress_activit.svg";
|
||||
import undo from "assets/images/undo.svg";
|
||||
import group_add from "assets/images/group_add.svg";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
selectTypistGroups,
|
||||
selectIsLoading,
|
||||
listTypistGroupsAsync,
|
||||
} from "features/workflow/typistGroup";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getTranslationID } from "translation";
|
||||
|
||||
const TypistGroupSettingPage: React.FC = (): JSX.Element => (
|
||||
<div className={styles.wrap}>
|
||||
<Header userName="XXXXXX" />
|
||||
<UpdateTokenTimer />
|
||||
<main className={styles.main}>
|
||||
<div className="">Transcriptionist Group Setting</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
const TypistGroupSettingPage: React.FC = (): JSX.Element => {
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const [t] = useTranslation();
|
||||
|
||||
const isLoading = useSelector(selectIsLoading);
|
||||
const typistGroup = useSelector(selectTypistGroups);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(listTypistGroupsAsync());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<Header userName="XXXXXX" />
|
||||
<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("typistGroupSetting.label.title")
|
||||
)}`}</p>
|
||||
</div>
|
||||
|
||||
<section className={styles.workflow}>
|
||||
<div>
|
||||
<ul className={styles.menuAction}>
|
||||
<li>
|
||||
<a
|
||||
href="/workflow"
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
>
|
||||
<img src={undo} alt="" className={styles.menuIcon} />
|
||||
{t(getTranslationID("typistGroupSetting.label.return"))}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a className={`${styles.menuLink} ${styles.isActive}`}>
|
||||
<img src={group_add} alt="" className={styles.menuIcon} />
|
||||
|
||||
{t(getTranslationID("typistGroupSetting.label.addGroup"))}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<table className={`${styles.table} ${styles.group}`}>
|
||||
<tr className={styles.tableHeader}>
|
||||
<th className={styles.noLine}>
|
||||
{t(getTranslationID("typistGroupSetting.label.groupName"))}
|
||||
</th>
|
||||
<th>{/** empty th */}</th>
|
||||
</tr>
|
||||
{!isLoading && typistGroup.length === 0 ? (
|
||||
<p style={{ margin: "10px", textAlign: "center" }}>
|
||||
{t(getTranslationID("common.message.listEmpty"))}
|
||||
</p>
|
||||
) : (
|
||||
typistGroup.map((group) => (
|
||||
<tr key={group.id}>
|
||||
<td>{group.name}</td>
|
||||
<td>
|
||||
<ul
|
||||
className={`${styles.menuAction} ${styles.inTable}`}
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
className={`${styles.menuLink} ${styles.isActive}`}
|
||||
>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"typistGroupSetting.label.edit"
|
||||
)
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</table>
|
||||
{isLoading && (
|
||||
<img
|
||||
src={progress_activit}
|
||||
className={styles.icLoading}
|
||||
alt="Loading"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TypistGroupSettingPage;
|
||||
|
||||
@ -352,5 +352,19 @@
|
||||
"inputEmptyError": "(de)この項目の入力は必須です。入力してください。",
|
||||
"licenseAllocationFailure": "(de)ライセンスの割り当てに失敗しました。他のライセンスを選択して再度割り当てをしてください。"
|
||||
}
|
||||
},
|
||||
"workflowPage": {
|
||||
"label": {
|
||||
"title": "(de)Workflow"
|
||||
}
|
||||
},
|
||||
"typistGroupSetting": {
|
||||
"label": {
|
||||
"title": "(de)Transctiprionist Group",
|
||||
"return": "(de)Return",
|
||||
"addGroup": "(de)Add Group",
|
||||
"groupName": "(de)Group Name",
|
||||
"edit": "(de)Edit"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -352,5 +352,19 @@
|
||||
"inputEmptyError": "この項目の入力は必須です。入力してください。",
|
||||
"licenseAllocationFailure": "ライセンスの割り当てに失敗しました。他のライセンスを選択して再度割り当てをしてください。"
|
||||
}
|
||||
},
|
||||
"workflowPage": {
|
||||
"label": {
|
||||
"title": "Workflow"
|
||||
}
|
||||
},
|
||||
"typistGroupSetting": {
|
||||
"label": {
|
||||
"title": "Transctiprionist Group",
|
||||
"return": "Return",
|
||||
"addGroup": "Add Group",
|
||||
"groupName": "Group Name",
|
||||
"edit": "Edit"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -352,5 +352,19 @@
|
||||
"inputEmptyError": "(es)この項目の入力は必須です。入力してください。",
|
||||
"licenseAllocationFailure": "(es)ライセンスの割り当てに失敗しました。他のライセンスを選択して再度割り当てをしてください。"
|
||||
}
|
||||
},
|
||||
"workflowPage": {
|
||||
"label": {
|
||||
"title": "(es)Workflow"
|
||||
}
|
||||
},
|
||||
"typistGroupSetting": {
|
||||
"label": {
|
||||
"title": "(es)Transctiprionist Group",
|
||||
"return": "(es)Return",
|
||||
"addGroup": "(es)Add Group",
|
||||
"groupName": "(es)Group Name",
|
||||
"edit": "(es)Edit"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -352,5 +352,19 @@
|
||||
"inputEmptyError": "(fr)この項目の入力は必須です。入力してください。",
|
||||
"licenseAllocationFailure": "(fr)ライセンスの割り当てに失敗しました。他のライセンスを選択して再度割り当てをしてください。"
|
||||
}
|
||||
},
|
||||
"workflowPage": {
|
||||
"label": {
|
||||
"title": "(fr)Workflow"
|
||||
}
|
||||
},
|
||||
"typistGroupSetting": {
|
||||
"label": {
|
||||
"title": "(fr)Transctiprionist Group",
|
||||
"return": "(fr)Return",
|
||||
"addGroup": "(fr)Add Group",
|
||||
"groupName": "(fr)Group Name",
|
||||
"edit": "(fr)Edit"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user