Skip to content

Commit

Permalink
Feature/Adapt product template user interface (#462)
Browse files Browse the repository at this point in the history
* show input fields for responsible and participants only if only one product is selected
use input tags for multiple adding of participants

* removed checkboxes for chapter entries in tree
take "Themenbeschreibungen einfügen" option into account when generating product template

* added select/deselect all entries button to product template tree

* comment out discipline info popover for now till data is not loaded at this time
  • Loading branch information
Bock4Soft authored Aug 13, 2023
1 parent 10eb160 commit 0242ee0
Show file tree
Hide file tree
Showing 8 changed files with 577 additions and 220 deletions.
229 changes: 215 additions & 14 deletions client/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"react-router": "6.9.0",
"react-router-dom": "6.9.0",
"react-router-hash-link": "^2.4.3",
"react-tag-input": "^6.8.1",
"react-xml-parser": "github:Bock4Soft/xml-parser",
"rxjs": "7.8.0",
"xml-js": "^1.6.11",
Expand All @@ -74,6 +75,7 @@
"@types/react-router": "5.1.13",
"@types/react-router-dom": "5.1.7",
"@types/react-router-hash-link": "^2.4.6",
"@types/react-tag-input": "^6.6.1",
"@unocss/preset-mini": "0.48.3",
"@unocss/vite": "0.48.3",
"@unocss/webpack": "0.48.3",
Expand Down
1 change: 1 addition & 0 deletions client/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<base href="/" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="reactTags.css">
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
Expand Down
85 changes: 85 additions & 0 deletions client/public/reactTags.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@

.ReactTags__tags {
position: relative;
}

.ReactTags__clearAll {
cursor: pointer;
padding: 10px;
margin: 10px;
background: #f88d8d;
color: #fff;
border: none;
}

/* Styles for the input */
.ReactTags__tagInput {
border-radius: 2px;
}
.ReactTags__tagInput input.ReactTags__tagInputField {
box-sizing: border-box;
margin: 0;
padding: 4px 11px;
color: rgba(0, 0, 0, 0.88);
font-size: 14px;
line-height: 1.5714285714285714;
list-style: none;
font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol','Noto Color Emoji';
position: relative;
display: inline-block;
width: 100%;
min-width: 0;
background-color: #ffffff;
background-image: none;
border-width: 1px;
border-style: solid;
border-color: #d9d9d9;
border-radius: 6px;
transition: all 0.2s;
}

.ReactTags__tagInput input.ReactTags__tagInputField:hover {
border-color: #4096ff;
border-inline-end-width: 1px;
}

.ReactTags__tagInput input.ReactTags__tagInputField:focus {
border-color: #4096ff;
box-shadow: 0 0 0 2px rgba(5, 145, 255, 0.1);
border-inline-end-width: 1px;
outline: 0;
}

.ReactTags__editInput {
border-radius: 1px;
}

.ReactTags__editTagInput {
display: inline-flex;
}

/* Styles for selected tags */
.ReactTags__selected span.ReactTags__tag {
background: #c6c6c6;
color: black;
font-size: 14px;
display: inline-block;
padding: 5px 8px;
margin: 0 5px 5px;
border-radius: 15px;
}
.ReactTags__selected a.ReactTags__remove {
color: black;
font-size: 14px;
margin-left: 5px;
cursor: pointer;
}


.ReactTags__remove {
border: none;
cursor: pointer;
background: none;
color: black;
font-size: 14px;
}
209 changes: 74 additions & 135 deletions client/src/components/projekthandbuch/produktvorlagen/SubmitArea.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
import { Button, Checkbox, Form, Input, Modal, Space, Tag } from 'antd';
import { Button, Checkbox, Form } from 'antd';
import React, { useState } from 'react';
import { useTemplate } from '../../../context/TemplateContext';
import { OperationOpts, SingleProduct } from '@dipa-projekt/projektassistent-openapi';
import { MultiProducts } from '@dipa-projekt/projektassistent-openapi/dist/models/MultiProducts';
import { ProductOfProject } from '@dipa-projekt/projektassistent-openapi/dist/models';
import API from '../../../api';
import { AjaxResponse } from 'rxjs/ajax';
import { TemplateFormModal } from './TemplateFormModal';

export function SubmitArea() {
const { checkedKeys, topicsMap } = useTemplate();
const { checkedKeys, productsMap, insertTopicDescription, setInsertTopicDescription } = useTemplate();

const [modalVisible, setModalVisible] = useState(false);
const [formTitle, setFormTitle] = useState();
const [confirmLoading, setConfirmLoading] = useState();

const [responsible, setResponsible] = useState<string>();
const [projectName, setProjectName] = useState<string>();
const [participants, setParticipants] = useState<string[]>([]);
const [selectedProducts, setSelectedProducts] = useState<ProductOfProject[]>([]);

const buttonItemLayout = {
wrapperCol: {
Expand All @@ -25,86 +22,89 @@ export function SubmitArea() {
},
};

function collectDataByProduct(values: {
participants: string;
function collectDataForSingleProduct(values: {
projectName: string;
responsible: string;
}): SingleProduct | MultiProducts | undefined {
const productsMap = new Map<string, ProductOfProject>();
participants: string[];
}): SingleProduct {
return Object.assign(selectedProducts[0], {
projectName: values.projectName,
responsible: values.responsible,
participants: values.participants,
}) as SingleProduct;
}

function collectDataForMultiProducts(values: { projectName: string }): MultiProducts {
return {
projectName: values.projectName,
products: selectedProducts,
} as MultiProducts;
}

function getChaptersData(topics: { title: string; text: string }[]) {
if (insertTopicDescription) {
return topics;
} else {
return topics.map((obj) => ({ ...obj, text: undefined }));
}
}

function doSubmit(): void {
setModalVisible(true);

const productOfProjectMap = new Map<string, ProductOfProject>();

for (const checkedKey of checkedKeys) {
const topic = topicsMap.get(checkedKey);
if (topic) {
if (!productsMap.has(topic.product.id)) {
productsMap.set(topic.product.id, {
productName: topic.product.title,
responsible: values.responsible,
participants: values.participants ? [values.participants] : [],
chapters: [],
});
}
productsMap.get(topic.product.id)!.chapters.push(topic.topic);
const product = productsMap.get(checkedKey);
if (product) {
productOfProjectMap.set(product.product.id, {
productName: product.product.title,
responsible: '',
participants: [],
chapters: getChaptersData(product.topics),
});
}
}

const products: ProductOfProject[] = [];

if (productsMap.size > 0) {
for (const productKey of productsMap.keys()) {
const product = productsMap.get(productKey);
if (product) {
products.push(product);
}
}
if (products.length > 1) {
return {
projectName: values.projectName,
products: products,
} as MultiProducts;
} else {
const singleProductOfProject = products[0];
const singleProduct = Object.assign(singleProductOfProject, {
projectName: values.projectName,
}) as SingleProduct;
return singleProduct;
for (const productKey of productOfProjectMap.keys()) {
const product = productOfProjectMap.get(productKey);
if (product) {
products.push(product);
}
}
return undefined;
}

function doSubmit(): void {
setModalVisible(true);
setSelectedProducts(products);
}

const handleCancel = (values) => {
console.log('Received values of form: ', values);
setModalVisible(false);
};

const handleOk = (values: any) => {
const handleClose = (values: { projectName: string; responsible: string; participants: string[] }) => {
console.log('Received values of form: ', values);

setProjectName(values.projectName);
setResponsible(values.responsible);
setParticipants(values.participants);

setModalVisible(false);

const opts: OperationOpts = { responseOpts: { response: 'raw' } };
if (values) {
const opts: OperationOpts = { responseOpts: { response: 'raw' } };

const bodyData = collectDataByProduct(values);
if (bodyData.hasOwnProperty('products')) {
API.ProductsApi.getZipForMultiProducts({ multiProducts: bodyData as MultiProducts }, opts).subscribe(
(response: AjaxResponse<Blob>) => {
if (selectedProducts.length > 1) {
API.ProductsApi.getZipForMultiProducts(
{
multiProducts: collectDataForMultiProducts(values),
},
opts
).subscribe((response: AjaxResponse<Blob>) => {
downloadFile(response);
}
);
} else {
API.ProductsApi.getDocxForSingleProduct({ singleProduct: bodyData as SingleProduct }, opts).subscribe(
(response: AjaxResponse<Blob>) => {
});
} else {
API.ProductsApi.getDocxForSingleProduct(
{
singleProduct: collectDataForSingleProduct(values),
},
opts
).subscribe((response: AjaxResponse<Blob>) => {
downloadFile(response);
}
);
});
}
}
};

Expand All @@ -128,28 +128,23 @@ export function SubmitArea() {
link.remove();
}

function onChangeInsertTopicDescription() {
setInsertTopicDescription(!insertTopicDescription);
}

return (
<>
<div className="sticky-wrapper" style={{ padding: '24px' }}>
<div style={{ marginTop: '10px' }}>
<Checkbox
// indeterminate={indeterminateProductTemplates}
// onChange={(e: CheckboxChangeEvent) => {
// this.ctrl.produktvorlagenService.setCheckAllProductTemplates(e.target.checked);
// }}
// checked={this.ctrl.checkAllProductTemplates}
>
Alle <Tag color="blue">Produktvorlagen</Tag>auswählen
</Checkbox>
</div>
{/*TODO: reinsert if there are Mustertexte available*/}
{/*<Form.Item style={{ marginTop: '30px' }}>*/}
{/* <Checkbox>*/}
{/* <Tag color="red">Mustertexte</Tag> einfügen*/}
{/* </Checkbox>*/}
{/*</Form.Item>*/}
<Form.Item>
<Checkbox>Themenbeschreibungen einfügen</Checkbox>
<Checkbox checked={insertTopicDescription} onChange={onChangeInsertTopicDescription}>
Themenbeschreibungen einfügen
</Checkbox>
</Form.Item>
<Form.Item {...buttonItemLayout}>
<Button
Expand All @@ -164,63 +159,7 @@ export function SubmitArea() {
</Form.Item>
</div>

<Modal
title={formTitle}
open={modalVisible}
confirmLoading={confirmLoading}
onCancel={handleCancel}
footer={[]} //this to hide the default inputs of the modal
>
<Form
layout="vertical"
name="form_in_modal"
initialValues={
{
// modifier: 'public',
}
}
onFinish={handleOk}
>
<Form.Item
name="projectName"
label="Projektname"
rules={[
{
required: true,
message: 'Bitte geben Sie einen Projektnamen ein.',
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="responsible"
label="Verantwortlich"
rules={[
{
required: true,
message: 'Bitte geben Sie einen oder mehrere Verantwortliche ein.',
},
]}
>
<Input />
</Form.Item>
<Form.Item name="participants" label="Mitwirkend">
<Input />
</Form.Item>
<Space wrap>
<Button key="submit" type="primary" loading={confirmLoading} htmlType="submit">
Erzeugen
</Button>
<Button key="cancel" type="default" onClick={handleCancel} htmlType="button">
Abbrechen
</Button>
<Button key="reset" type="ghost" htmlType="reset">
Zurücksetzen
</Button>
</Space>
</Form>
</Modal>
<TemplateFormModal products={selectedProducts} open={modalVisible} handleClose={handleClose} />
</>
);
}
Loading

0 comments on commit 0242ee0

Please sign in to comment.