Set region information for static text, one example
@ -11,13 +11,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@vueup/vue-quill": "^1.2.0",
|
||||
"axios": "^1.4.0",
|
||||
"bootstrap": "^5.3.1",
|
||||
"buffer": "^6.0.3",
|
||||
"pdfjs-dist": "^4.2.67",
|
||||
"pdfjs-dist": "^4.3.136",
|
||||
"pinia": "^2.1.6",
|
||||
"quill": "^1.3.7",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4",
|
||||
"vuex": "^4.1.0"
|
||||
@ -28,12 +26,12 @@
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"fast-glob": "^3.3.2",
|
||||
"prettier": "^3.0.0",
|
||||
"sass": "^1.66.1",
|
||||
"sass-loader": "^13.3.2",
|
||||
"vite": "^4.4.6",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"fast-glob": "^3.3.2",
|
||||
"webpack": "^5.88.2"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
frontend/src/assets/images/lab-translation.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
frontend/src/assets/images/qr-code.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
frontend/src/assets/images/submited.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
@ -1,40 +1,48 @@
|
||||
.stop-button {
|
||||
@extend .btn;
|
||||
@extend .btn-secondary;
|
||||
@extend .btn-outline-secondary;
|
||||
@extend .btn-sm;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
@extend .btn;
|
||||
@extend .btn-secondary;
|
||||
@extend .btn-outline-secondary;
|
||||
@extend .btn-sm;
|
||||
}
|
||||
|
||||
.proceed-button {
|
||||
@extend .btn;
|
||||
@extend .btn-primary;
|
||||
@extend .btn-sm;
|
||||
}
|
||||
|
||||
.initiate-button {
|
||||
@extend .btn;
|
||||
@extend .btn-primary;
|
||||
@extend .btn-sm;
|
||||
}
|
||||
|
||||
.proceed-button {
|
||||
@extend .btn;
|
||||
@extend .btn-primary;
|
||||
@extend .btn-sm;
|
||||
}
|
||||
|
||||
.option-button {
|
||||
@extend .btn;
|
||||
@extend .btn-primary;
|
||||
@extend .btn-sm;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
@extend .btn-close;
|
||||
@extend .btn-sm;
|
||||
}
|
||||
|
||||
.light-button {
|
||||
@extend .btn;
|
||||
@extend .btn-light;
|
||||
@extend .btn-sm;
|
||||
}
|
||||
|
||||
.inplace-proceed-button {
|
||||
@ -44,3 +52,7 @@
|
||||
.inplace-back-button {
|
||||
@extend .light-button;
|
||||
}
|
||||
|
||||
.min-btn {
|
||||
min-width: 115px;
|
||||
}
|
||||
|
||||
@ -104,3 +104,131 @@ p {
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.btn-link {
|
||||
text-decoration: none;
|
||||
color: $primary;
|
||||
}
|
||||
.dropdown-menu {
|
||||
border: none;
|
||||
box-shadow: 0px 0px 24px 0px #d4d3e380;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
.btn-link {
|
||||
text-decoration: none;
|
||||
height: 33px;
|
||||
width: 100%;
|
||||
color: #0d1637;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
border-radius: 0;
|
||||
&:hover {
|
||||
background-color: #f3f6ff;
|
||||
}
|
||||
&.active {
|
||||
background-color: #1748f8;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
.dropdown-new {
|
||||
color: #1748f8;
|
||||
&:hover {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
.dropdown-new-input-container {
|
||||
padding: 6px 12px;
|
||||
position: relative;
|
||||
input {
|
||||
border: 1px solid #e7e8eb;
|
||||
box-shadow: none;
|
||||
border-radius: 3px;
|
||||
padding: 0 20px 0 5px;
|
||||
height: 22px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.dropdown-new-input-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 3px;
|
||||
color: #3d455f;
|
||||
background-color: #eeeff1;
|
||||
border-radius: 3px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
@media (min-width: 1200px) {
|
||||
max-width: $body-width;
|
||||
}
|
||||
}
|
||||
|
||||
.accordion-list {
|
||||
box-shadow: 0px 0px 24px 0px #d4d3e380;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 16px;
|
||||
.accordion-item {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.accordion-button {
|
||||
padding: 12px 28px 12px 12px;
|
||||
outline: none;
|
||||
box-shadow: none !important;
|
||||
.dashed-container {
|
||||
flex: 1;
|
||||
margin-right: 28px;
|
||||
padding: 8px 12px;
|
||||
font-weight: bold;
|
||||
border: 1px dashed #aebffd;
|
||||
border-radius: 3px;
|
||||
}
|
||||
&:not(.collapsed) {
|
||||
color: black;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
.dashed-container {
|
||||
background-color: #f3f6ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submission-result-container {
|
||||
@extend .flex-row-container;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: $body-height;
|
||||
}
|
||||
|
||||
.submission-result-card {
|
||||
width: fit-content;
|
||||
min-width: 641px;
|
||||
padding: 45px 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 26px;
|
||||
line-height: 36px;
|
||||
box-shadow: 0px 0px 24px 0px #d4d3e380;
|
||||
|
||||
img {
|
||||
width: 86px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.btn-link {
|
||||
text-decoration: underline;
|
||||
padding: 0;
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-contianer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
520
frontend/src/components/FreeleapsEditor.vue
Normal file
@ -0,0 +1,520 @@
|
||||
<template>
|
||||
<div class="freeleaps-editor">
|
||||
<div
|
||||
class="editor-body"
|
||||
:contenteditable="!disabled"
|
||||
spellcheck="false"
|
||||
ref="editor"
|
||||
v-html="content"
|
||||
@blur="updateAction"
|
||||
@mouseup.stop="selectionChange"
|
||||
/>
|
||||
<div v-if="!disabled" class="editor-control" :style="editorCtrlStyle">
|
||||
<div v-for="(item, index) in iconList" :key="index" class="editor-item">
|
||||
<button
|
||||
class="item-icon"
|
||||
:class="{
|
||||
activity: commandStates.indexOf(item.type) !== -1,
|
||||
last: index === iconList.length - 1
|
||||
}"
|
||||
:data-info="item.name"
|
||||
@click="iconClick($event, item.type)"
|
||||
:data-bs-toggle="item.drop ? 'dropdown' : ''"
|
||||
data-bs-auto-close="outside"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<svg-icon :icon="item.icon" class-name="icon" />
|
||||
</button>
|
||||
<div class="dropmenu drop-style" v-if="item.type === 'style'">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'p', 'style')"><p>Text</p></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'pre', 'style')"><pre>code</pre></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'blockquote', 'style')"
|
||||
><blockquote>引用</blockquote></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h1', 'style')"><h1>Caption 1</h1></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h2', 'style')"><h2>Caption 2</h2></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h3', 'style')"><h3>Caption 3</h3></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h4', 'style')"><h4>Caption 4</h4></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h5', 'style')"><h5>Caption 5</h5></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'h6', 'style')"><h6>Caption 6</h6></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="dropmenu drop-style" v-if="item.type === 'alignjustify'">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'justifyCenter', 'alignjustify')">
|
||||
<i class="iconfont icon-aligncenter"></i>
|
||||
<span>middle</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'justifyLeft', 'alignjustify')">
|
||||
<i class="iconfont icon-alignleft"></i>
|
||||
<span>left aligned</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'justifyRight', 'alignjustify')">
|
||||
<i class="iconfont icon-alignright"></i>
|
||||
<span>right aligned</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" @click="iconClick($event, 'justifyFull', 'alignjustify')">
|
||||
<i class="iconfont icon-alignjustify"></i>
|
||||
<span>default aligned</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import SvgIcon from '@/components/SvgIcon.vue'
|
||||
export default {
|
||||
name: 'FreeleapsEditor',
|
||||
props: {
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['update:content'],
|
||||
components: { SvgIcon },
|
||||
data() {
|
||||
return {
|
||||
selectedRange: '',
|
||||
editorCtrlStyle: {},
|
||||
commandStates: [],
|
||||
iconList: [
|
||||
// {
|
||||
// name: 'lable', // hover name
|
||||
// type: 'style', // click event handler
|
||||
// icon: 'fe-paragraph', // icon style
|
||||
// drop: true, // If there is drop menu
|
||||
// canChoose: true, // chosen or not
|
||||
// },
|
||||
{
|
||||
name: 'bold',
|
||||
type: 'bold',
|
||||
icon: 'fe-bold',
|
||||
drop: false,
|
||||
canChoose: true
|
||||
},
|
||||
{
|
||||
name: 'italic',
|
||||
type: 'italic',
|
||||
icon: 'fe-italic',
|
||||
drop: false,
|
||||
canChoose: true
|
||||
},
|
||||
{
|
||||
name: 'underline',
|
||||
type: 'underline',
|
||||
icon: 'fe-underline',
|
||||
drop: false,
|
||||
canChoose: true
|
||||
},
|
||||
// {
|
||||
// name: 'strike',
|
||||
// type: 'strike',
|
||||
// icon: 'fe-strike',
|
||||
// drop: false,
|
||||
// canChoose: true,
|
||||
// },
|
||||
// {
|
||||
// name: 'clear-format',
|
||||
// type: 'clear',
|
||||
// icon: 'fe-clear',
|
||||
// drop: false,
|
||||
// canChoose: false,
|
||||
// },
|
||||
// {
|
||||
// name: 'font-color',
|
||||
// type: 'fontFamily',
|
||||
// icon: 'fe-char',
|
||||
// drop: false,
|
||||
// canChoose: true,
|
||||
// },
|
||||
{
|
||||
name: 'unordered-list',
|
||||
type: 'unorderedlist',
|
||||
icon: 'fe-unorderedlist',
|
||||
drop: false,
|
||||
canChoose: true
|
||||
},
|
||||
{
|
||||
name: 'ordered list',
|
||||
type: 'orderedlist',
|
||||
icon: 'fe-orderedlist',
|
||||
drop: false,
|
||||
canChoose: true
|
||||
}
|
||||
// {
|
||||
// name: 'align-justify',
|
||||
// type: 'alignjustify',
|
||||
// icon: 'fe-alignjustify',
|
||||
// drop: true,
|
||||
// canChoose: true,
|
||||
// }
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectionChange(e) {
|
||||
const sel = window.getSelection()
|
||||
if (sel && sel.type === 'Range') {
|
||||
// this.selectedRange = sel.getRangeAt(0)
|
||||
// this.restoreSelection()
|
||||
// console.log('window.getSelection',this.selectedRange)
|
||||
// console.log('is bold ? ', this.queryCommandState(sel.getRangeAt(0), 'bold'))
|
||||
// this.iconClick(null, 'bold')
|
||||
this.editorCtrlStyle = { display: 'flex', top: `${e.layerY}px`, left: `${e.layerX}px` }
|
||||
} else {
|
||||
this.selectedRange = null
|
||||
this.editorCtrlStyle = { display: 'none' }
|
||||
// this.commandStates = []
|
||||
}
|
||||
},
|
||||
queryCommandState(range, command) {
|
||||
const container = document.createElement('span')
|
||||
container.style.display = 'none'
|
||||
|
||||
container.appendChild(range.cloneContents())
|
||||
console.log('queryCommandState', container)
|
||||
// range
|
||||
document.body.appendChild(container)
|
||||
const state = document.queryCommandState(command)
|
||||
console.log('queryCommandIndeterm ', document.queryCommandIndeterm('bold'))
|
||||
console.log('queryCommandState ', document.queryCommandState('bold'))
|
||||
// range.collapse(true);
|
||||
container.remove()
|
||||
return state
|
||||
},
|
||||
iconClick(event, type, dropType) {
|
||||
event.preventDefault()
|
||||
this.$refs.editor.focus()
|
||||
this.selectedRange = this.getSelect()
|
||||
this.restoreSelection()
|
||||
this.changeStyle(type)
|
||||
this.$nextTick(() => {
|
||||
if (dropType) {
|
||||
type = dropType
|
||||
}
|
||||
const i = this.commandStates.indexOf(type)
|
||||
if (i === -1) {
|
||||
this.commandStates.push(type)
|
||||
} else {
|
||||
this.commandStates.splice(i, 1)
|
||||
}
|
||||
})
|
||||
},
|
||||
getSelect() {
|
||||
if (window.getSelection) {
|
||||
let sel = window.getSelection()
|
||||
console.log('this.selectedRange', sel)
|
||||
if (sel.rangeCount > 0) {
|
||||
return sel.getRangeAt(0)
|
||||
}
|
||||
} else if (document.selection) {
|
||||
return document.selection.createRange()
|
||||
}
|
||||
return null
|
||||
},
|
||||
// change the selection's style
|
||||
changeStyle(type) {
|
||||
switch (type) {
|
||||
case 'bold':
|
||||
document.execCommand('bold', false)
|
||||
break
|
||||
case 'underline':
|
||||
document.execCommand('underline', false)
|
||||
break
|
||||
case 'strike':
|
||||
document.execCommand('strikeThrough', false)
|
||||
break
|
||||
case 'italic':
|
||||
document.execCommand('italic', false)
|
||||
break
|
||||
case 'clear':
|
||||
document.execCommand('removeFormat', false)
|
||||
break
|
||||
case 'unorderedlist':
|
||||
document.execCommand('insertUnorderedList', false)
|
||||
break
|
||||
case 'orderedlist':
|
||||
document.execCommand('insertorderedList', false)
|
||||
break
|
||||
case 'h1':
|
||||
case 'h2':
|
||||
case 'h3':
|
||||
case 'h4':
|
||||
case 'h5':
|
||||
case 'h6':
|
||||
case 'p':
|
||||
case 'pre':
|
||||
case 'blockquote':
|
||||
document.execCommand('formatBlock', false, type)
|
||||
break
|
||||
case 'justifyCenter':
|
||||
case 'justifyFull':
|
||||
case 'justifyLeft':
|
||||
case 'justifyRight':
|
||||
document.execCommand(type, false)
|
||||
break
|
||||
default:
|
||||
console.log('none')
|
||||
}
|
||||
// window.getSelection().removeAllRanges()
|
||||
},
|
||||
restoreSelection() {
|
||||
let selection = window.getSelection()
|
||||
if (this.selectedRange) {
|
||||
try {
|
||||
selection.removeAllRanges()
|
||||
} catch (ex) {
|
||||
document.body.createTextRange().select()
|
||||
document.selection.empty()
|
||||
}
|
||||
selection.addRange(this.selectedRange)
|
||||
}
|
||||
},
|
||||
|
||||
updateAction($event) {
|
||||
let html = $event.target.innerHTML || ''
|
||||
this.$emit('update:content', html)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.freeleaps-editor {
|
||||
position: relative;
|
||||
min-width: 100px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
.h1,
|
||||
.h2,
|
||||
.h3,
|
||||
.h4,
|
||||
.h5,
|
||||
.h6,
|
||||
p,
|
||||
a {
|
||||
font-family: inherit;
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
color: inherit;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||
}
|
||||
.editor-control {
|
||||
display: none;
|
||||
width: fit-content;
|
||||
height: 26px;
|
||||
background-color: #f8f8f9;
|
||||
position: absolute;
|
||||
}
|
||||
.dropmenu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
left: 0;
|
||||
min-width: 160px;
|
||||
padding: 5px 0;
|
||||
margin: 2px 0 0;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
list-style: none;
|
||||
background-color: #fff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
ul {
|
||||
padding: 10px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
ul li {
|
||||
text-align: left;
|
||||
list-style: none;
|
||||
a {
|
||||
display: block;
|
||||
padding: 5px 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
.drop-align {
|
||||
min-width: 100px;
|
||||
}
|
||||
.editor-body {
|
||||
height: 300px;
|
||||
padding: 10px;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
overflow: auto;
|
||||
outline: none;
|
||||
text-align: left;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #68747f;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editor-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
.item-icon {
|
||||
position: relative;
|
||||
width: 50px;
|
||||
height: 16px;
|
||||
font-size: 16px;
|
||||
color: #9ea2af;
|
||||
font-weight: normal;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
touch-action: manipulation;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
outline: none;
|
||||
transition: all 0.1s ease-out;
|
||||
border: none;
|
||||
line-height: 16px;
|
||||
background: transparent;
|
||||
border-right: 1px solid #e7e8eb;
|
||||
&.last {
|
||||
border-right: 0;
|
||||
}
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
content: attr(data-info);
|
||||
top: 40px;
|
||||
left: 20px;
|
||||
padding: 5px 8px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
line-height: 1.5;
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
-webkit-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
visibility: hidden;
|
||||
opacity: 0.9;
|
||||
letter-spacing: 1px;
|
||||
z-index: 9999;
|
||||
}
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 35px;
|
||||
left: 20px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin: 0 0 0 -6px;
|
||||
font-size: 0;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
border-bottom: 6px solid currentColor;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
visibility: hidden;
|
||||
opacity: 0.9;
|
||||
z-index: 9999;
|
||||
}
|
||||
&:hover,
|
||||
&.activity {
|
||||
color: black;
|
||||
}
|
||||
&:hover:after,
|
||||
&:hover:before {
|
||||
visibility: visible;
|
||||
}
|
||||
&.activity + .dropmenu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.freeleaps-editor blockquote {
|
||||
padding: 10px 20px;
|
||||
font-size: 17.5px;
|
||||
border-left: 5px solid #f86466;
|
||||
background: white;
|
||||
}
|
||||
.freeleaps-editor pre {
|
||||
display: block;
|
||||
padding: 9.5px;
|
||||
font-size: 13px;
|
||||
line-height: 1.42857143;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.editor-body blockquote {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.editor-body pre {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.editor-body ul {
|
||||
padding-left: 40px;
|
||||
list-style-type: disc;
|
||||
}
|
||||
.editor-body ol {
|
||||
padding-left: 40px;
|
||||
}
|
||||
</style>
|
||||
105
frontend/src/components/InputSelector.vue
Normal file
@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="input-selector-container">
|
||||
<div
|
||||
class="input-selector-btn"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
id="input-selector-btn"
|
||||
>
|
||||
<span>{{ selected || '' }}</span>
|
||||
<svg-icon icon="dropdown" class-name="selector-dropdown-icon" />
|
||||
</div>
|
||||
<ul class="dropdown-menu" aria-labelledby="input-selector-btn">
|
||||
<li>
|
||||
<button v-if="!inputing" class="btn btn-link dropdown-new" @click="newAction">+ NEW</button>
|
||||
<div v-if="inputing" class="dropdown-new-input-container">
|
||||
<input type="text" v-model="newval" @keyup.enter="newItemAction" />
|
||||
<svg-icon v-if="newval" icon="msg-enter" class-name="dropdown-new-input-icon" />
|
||||
</div>
|
||||
</li>
|
||||
<li v-for="item in selectList" :key="item">
|
||||
<button
|
||||
class="btn btn-link"
|
||||
@click="selectItem(item)"
|
||||
:class="item == selected ? 'active' : ''"
|
||||
>
|
||||
{{ item }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SvgIcon from '@/components/SvgIcon.vue'
|
||||
export default {
|
||||
name: 'InputSelector',
|
||||
props: {
|
||||
selectList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
selected: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputing: true,
|
||||
newval: ''
|
||||
}
|
||||
},
|
||||
mounted() {},
|
||||
components: { SvgIcon },
|
||||
methods: {
|
||||
newAction($event) {
|
||||
$event.stopPropagation()
|
||||
this.newval = ''
|
||||
this.inputing = true
|
||||
},
|
||||
selectItem(item) {
|
||||
this.$emit('selectedChange', { selected: item, isNew: false })
|
||||
},
|
||||
newItemAction() {
|
||||
if (!this.newval) return
|
||||
this.$emit('selectedChange', {
|
||||
selected: this.newval,
|
||||
isNew: this.selectList.indexOf(this.newval) === -1
|
||||
})
|
||||
this.newval = ''
|
||||
this.inputing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.input-selector-container {
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
position: relative;
|
||||
.input-selector-btn {
|
||||
width: 153px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
border: 1px solid #e7e8eb;
|
||||
border-radius: 3px;
|
||||
padding: 0 3px 0 5px;
|
||||
> span {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #242424;
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
.selector-dropdown-icon {
|
||||
color: #242424;
|
||||
padding: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,81 +1,91 @@
|
||||
<template>
|
||||
<div class="pdf-reader">
|
||||
<canvas ref="canvas"></canvas>
|
||||
<div id="pdf-container" class="pdf-container">
|
||||
<canvas id="theCanvas"></canvas>
|
||||
<div class="oprate">
|
||||
<button class="btn btn-link" @click="prev">prev</button>
|
||||
<span> {{ currentPage }} / {{ numPages }} </span>
|
||||
<button class="btn btn-link" @click="next">next</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import { getDocument } from 'pdfjs-dist'
|
||||
import * as PDFJS from 'pdfjs-dist'
|
||||
// import pdfjsWorker from "pdfjs-dist/build/pdf.worker.entry";
|
||||
import { ContentApi } from '@/utils/index'
|
||||
// PDFJS.GlobalWorkerOptions.workerSrc = import('pdfjs-dist/build/pdf.worker')
|
||||
import * as PDFJS from 'pdfjs-dist/build/pdf'
|
||||
PDFJS.GlobalWorkerOptions.workerPort = new Worker(
|
||||
new URL('pdfjs-dist/build/pdf.worker.mjs', import.meta.url),
|
||||
{ type: 'module' }
|
||||
)
|
||||
|
||||
// import * as PDFJS from 'pdfjs-dist/webpack.mjs'
|
||||
|
||||
export default {
|
||||
name: 'PDFReader',
|
||||
props: {
|
||||
document: { type: String, default: '' }
|
||||
doc: { type: Object, default: () => {} }
|
||||
},
|
||||
mounted() {
|
||||
this.renderPDF()
|
||||
data() {
|
||||
return {
|
||||
loadingTask: null,
|
||||
currentPage: 1,
|
||||
numPages: 0,
|
||||
selectedPage: 1
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
doc() {
|
||||
if (this.doc.url || this.doc.data) {
|
||||
this.renderPDF()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async renderPDF() {
|
||||
// const response = await DocumentApi.download(this.document)
|
||||
const response = await ContentApi.retrieve_blog_content(this.document)
|
||||
console.log('response', response)
|
||||
|
||||
// var fr = new FileReader();
|
||||
// fr.onload = async function(){
|
||||
// const doc = await getDocument({url: this.result}).promise
|
||||
// const page = await doc.getPage(1)
|
||||
// const canvas = this.$refs.canvas
|
||||
// const context = canvas.getContext('2d')
|
||||
// const viewport = page.getViewport({ scale: 1 })
|
||||
|
||||
// canvas.width = viewport.width
|
||||
// canvas.height = viewport.height
|
||||
|
||||
// await page.render({
|
||||
// canvasContext: context,
|
||||
// viewport
|
||||
// })
|
||||
// }
|
||||
// fr.readAsDataURL(response.data);
|
||||
|
||||
const doc = await PDFJS.getDocument({ data: response.data }).promise
|
||||
const page = await doc.getPage(1)
|
||||
const canvas = this.$refs.canvas
|
||||
renderPage(num) {
|
||||
const canvas = document.getElementById('theCanvas')
|
||||
const context = canvas.getContext('2d')
|
||||
const viewport = page.getViewport({ scale: 1 })
|
||||
|
||||
canvas.width = viewport.width
|
||||
canvas.height = viewport.height
|
||||
|
||||
await page.render({
|
||||
canvasContext: context,
|
||||
viewport
|
||||
const scale = 1.5
|
||||
this.loadingTask.promise.then((pdf) => {
|
||||
pdf.getPage(num).then((page) => {
|
||||
const viewport = page.getViewport({ scale })
|
||||
canvas.height = viewport.height
|
||||
canvas.width = viewport.width
|
||||
const renderContext = {
|
||||
canvasContext: context,
|
||||
viewport: viewport
|
||||
}
|
||||
page.render(renderContext)
|
||||
})
|
||||
})
|
||||
},
|
||||
base64StringToUint8Array(base64String) {
|
||||
var padding = '='.repeat((4 - (base64String.length % 4)) % 4)
|
||||
var base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
|
||||
|
||||
var rawData = atob(base64)
|
||||
var outputArray = new Uint8Array(rawData.length)
|
||||
|
||||
for (var i = 0; i < rawData.length; ++i) {
|
||||
outputArray[i] = rawData.charCodeAt(i)
|
||||
renderPDF() {
|
||||
this.loadingTask = PDFJS.getDocument(this.doc)
|
||||
this.loadingTask.promise.then((pdf) => {
|
||||
this.numPages = pdf.numPages
|
||||
this.renderPage(1)
|
||||
})
|
||||
},
|
||||
prev() {
|
||||
if (this.currentPage > 1) {
|
||||
this.currentPage--
|
||||
this.renderPage(this.currentPage)
|
||||
}
|
||||
},
|
||||
next() {
|
||||
if (this.currentPage < this.numPages) {
|
||||
this.currentPage++
|
||||
this.renderPage(this.currentPage)
|
||||
}
|
||||
return outputArray
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pdf-reader {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
<style lang="scss" scoped>
|
||||
.pdf-container {
|
||||
position: relative;
|
||||
.operate {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
<template>
|
||||
<div class="vue-quill-container">
|
||||
<QuillEditor
|
||||
ref="quillRef"
|
||||
v-model:content="quillData"
|
||||
:options="quillOptions"
|
||||
content-type="html"
|
||||
@text-change="quillChange()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const toolbarOptions = [
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
['blockquote', 'code-block'],
|
||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
||||
[{ script: 'sub' }, { script: 'super' }],
|
||||
[{ indent: '-1' }, { indent: '+1' }],
|
||||
[{ direction: 'rtl' }],
|
||||
[{ size: ['small', false, 'large', 'huge'] }],
|
||||
[{ header: [1, 2, 3, 4, 5, 6, false] }],
|
||||
[{ color: [] }, { background: [] }],
|
||||
[{ font: [] }],
|
||||
[{ align: [] }],
|
||||
['clean']
|
||||
]
|
||||
import { QuillEditor } from '@vueup/vue-quill'
|
||||
import 'quill/dist/quill.core.css'
|
||||
import 'quill/dist/quill.snow.css'
|
||||
import 'quill/dist/quill.bubble.css'
|
||||
|
||||
export default {
|
||||
name: 'VueQuill',
|
||||
props: {
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
emits: ['update:content'],
|
||||
components: { QuillEditor },
|
||||
data() {
|
||||
return {
|
||||
quillData: this.content,
|
||||
quillOptions: {
|
||||
theme: 'snow',
|
||||
placeholder: '请输入',
|
||||
modules: {
|
||||
toolbar: {
|
||||
container: toolbarOptions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
quillChange() {
|
||||
this.$emit('update:content', this.quillData)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.vue-quill-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100px;
|
||||
}
|
||||
</style>
|
||||
@ -1,26 +1,61 @@
|
||||
<template>
|
||||
<div class="header-container">
|
||||
<div class="header-content">
|
||||
<div class="information-bar" @click="gotoMessages">
|
||||
<div
|
||||
class="information-bar"
|
||||
@click="gotoMessages"
|
||||
:class="activePath == 'message' ? 'active' : ''"
|
||||
>
|
||||
<img alt="freeleaps logo" src="@/assets/message.png" />
|
||||
</div>
|
||||
<div class="navigation-container" role="navigation">
|
||||
<button class="navigation-item active" @click="gotoWorkspace">
|
||||
<button
|
||||
class="navigation-item"
|
||||
@click="gotoWorkspace"
|
||||
:class="activePath == 'Workspace' ? 'active' : ''"
|
||||
>
|
||||
<svg-icon icon="workspace" class-name="icon" />
|
||||
Workspace
|
||||
</button>
|
||||
<button class="navigation-item" @click="gotoRequests">
|
||||
<button
|
||||
class="navigation-item"
|
||||
@click="gotoRequests"
|
||||
:class="activePath == 'Requests' ? 'active' : ''"
|
||||
>
|
||||
<svg-icon icon="requests" class-name="icon" />
|
||||
Requests
|
||||
</button>
|
||||
<button class="navigation-item" @click="gotoProviders">
|
||||
<button
|
||||
class="navigation-item"
|
||||
@click="gotoProviders"
|
||||
:class="activePath == 'Providers' ? 'active' : ''"
|
||||
>
|
||||
<svg-icon icon="providers" class-name="icon" />
|
||||
Providers
|
||||
</button>
|
||||
<button class="navigation-item" @click="gotoIssueRequest">
|
||||
<button
|
||||
class="navigation-item"
|
||||
@click="gotoIssueRequest"
|
||||
:class="activePath == 'Post' ? 'active' : ''"
|
||||
>
|
||||
<svg-icon icon="post" class-name="icon" />
|
||||
Post
|
||||
</button>
|
||||
<div class="form-check form-switch header-switch-container">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="personal-earning-now-checkbox"
|
||||
disabled
|
||||
/>
|
||||
<label class="form-check-label" for="personal-earning-now-checkbox">
|
||||
<span>Providing service</span>
|
||||
</label>
|
||||
<div class="header-switch-desc">
|
||||
Please go to profile page to add money receiving method
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-container">
|
||||
<img
|
||||
@ -64,7 +99,13 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userIdentityNote: this.mnx_getUserIdentity()
|
||||
userIdentityNote: this.mnx_getUserIdentity(),
|
||||
activePath: this.$route.meta.activePath
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route(to) {
|
||||
this.activePath = to.meta.activePath
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -156,6 +197,9 @@ export default {
|
||||
top: 0;
|
||||
background-color: #f44837;
|
||||
}
|
||||
&.active {
|
||||
border: 1px solid $primary;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-container {
|
||||
@ -185,6 +229,21 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.header-switch-container {
|
||||
position: relative;
|
||||
|
||||
.header-switch-desc {
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
left: 0;
|
||||
bottom: -20px;
|
||||
font-size: 12px;
|
||||
color: #3D455f;
|
||||
white-space: nowrap;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.company-logo {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
|
||||
1
frontend/src/icons/btn-history.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1362" width="64" height="64"><path d="M512 81.465C274.223 81.465 81.465 274.222 81.465 512S274.221 942.535 512 942.535c237.776 0 430.535-192.756 430.535-430.535S749.777 81.465 512 81.465z m0 804.279c-206.413 0-373.743-167.331-373.743-373.743S305.588 138.257 512 138.257s373.743 167.331 373.743 373.744S718.412 885.744 512 885.744z m58.193-364.989c4.018-8.407 6.272-17.821 6.272-27.76 0-24.174-13.313-45.226-33-56.26V248.949c0-6.781-5.498-12.28-12.28-12.28h-38.372c-6.781 0-12.28 5.498-12.28 12.28v187.785c-19.686 11.034-33 32.088-33 56.26 0 35.603 28.862 64.465 64.465 64.465a64.251 64.251 0 0 0 24.82-4.959l177.381 177.382c4.797 4.797 12.571 4.797 17.367 0l15.194-15.194c4.797-4.795 4.797-12.569 0-17.367L570.192 520.755z"></path></svg>
|
||||
|
After Width: | Height: | Size: 821 B |
1
frontend/src/icons/btn-templates.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1523" width="64" height="64"><path d="M758.94993735 933.875H265.05006265c-76.72843323 0-144.04975104-72.15625991-144.04975103-154.40283381V223.87422138C121.00031161 147.61877182 182.9085958 90.125 265.05006265 90.125H581.37091262l5.78090947 4.88749603c92.07411987 78.67292102 153.14154456 140.68631304 230.39551537 219.25412622 24.43748016 24.75280288 50.3990192 51.18732477 79.4612266 80.249533l5.99112432 6.04367824v378.91233271c-0.05255393 82.24657388-67.32131699 154.40283381-144.04975103 154.4028338zM265.05006265 131.2745639c-59.64847366 0-102.90018713 38.94230732-102.90018714 92.59965748v555.59794482c0 59.28059783 49.0326221 113.20071597 102.90018714 113.20071598h493.8998747c53.81501111 0 102.90018713-53.92011814 102.90018714-113.20071598V417.58723914c-26.80239771-26.74984461-50.87200286-51.29243262-73.62776404-74.46862354-74.9416072-76.15034253-134.53752695-136.74478354-222.14458055-211.89660562H265.05006265z" fill="#4D4D4D" p-id="1524"></path><path d="M861.85012449 460.52362989h-174.95133921c-82.19402078 0-154.35027988-67.32131699-154.35027988-144.04975105V131.2745639a20.6010585 20.6010585 0 0 1 41.14956391 1e-8v185.19931493c0 53.81501111 53.97267205 102.90018713 113.20071597 102.90018715h174.95133921a20.6010585 20.6010585 0 1 1 0 41.1495639z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
frontend/src/icons/delete.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5138" width="64" height="64"><path d="M993.310029 179.7115H892.968039l-45.464399 813.855781c-0.938917 16.951554-14.971621 30.209501-31.940191 30.209501H209.743828c-16.970573 0-30.998272-13.247937-31.936188-30.17747l-46.19311-813.887812H32.346588c-17.689275 0-32.002252-14.322987-32.002252-31.996247 0-17.666252 14.312978-31.989239 32.002252-31.989239h120.931097c1.457423 0 2.888821 0.106104 4.294194 0.294287a32.138385 32.138385 0 0 1 4.266166-0.294287h167.931996V31.99024c0-17.666252 14.312978-31.989239 31.998248-31.989239h301.08603c17.685271 0 31.995245 14.322987 31.995245 31.989239v83.735774H862.722502c1.656618 0 3.301224 0.129126 4.919805 0.382373a32.19444 32.19444 0 0 1 4.953838-0.382373H993.310029c17.685271 0 31.999249 14.322987 31.99925 31.989239 0 17.673259-14.313978 31.996246-31.99925 31.996247zM630.852066 63.986487H393.767539v51.739527h237.084527V63.986487zM195.710123 179.7115l44.280243 780.079796H785.316911l43.558538-780.079796H195.710123zM701.535093 869.624242c-0.964942 17.024626-15.060708 30.178471-31.903156 30.178472-0.625611 0-1.21819-0.014014-1.840797-0.046045-17.659245-1.000978-31.126397-16.119742-30.12542-33.757967l33.652865-592.997099c1.00398-17.666252 16.37399-30.883159 33.750959-30.132426 17.655241 0.997975 31.122393 16.120743 30.124419 33.758968L701.535093 869.624242z m-189.207773 30.178472c-17.692278 0-31.998248-14.319984-31.998249-31.993244V274.816375c0-17.673259 14.305971-31.992242 31.998249-31.992242 17.683269 0 31.996246 14.318983 31.996246 31.992242v592.993095c0 17.673259-14.312978 31.993243-31.996246 31.993244z m-156.621951-0.046045a35.334506 35.334506 0 0 1-1.843801 0.046045c-16.843449 0-30.93521-13.153846-31.904156-30.178472L288.301544 276.628145c-1.000978-17.638225 12.469177-32.760993 30.122417-33.758968 17.220817-0.79878 32.749982 12.466174 33.753963 30.132426l33.64886 592.997099c1.001978 17.638225-12.465173 32.756989-30.121415 33.757967z" p-id="5139"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
1
frontend/src/icons/dropdown.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1331 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4137" width="64" height="64"><path d="M552.5504 896.512L45.1584 244.1216A102.4 102.4 0 0 1 125.952 78.848h1014.784a102.4 102.4 0 0 1 80.896 165.2736l-507.4944 652.288a102.4 102.4 0 0 1-161.6896 0z" p-id="4138"></path></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
1
frontend/src/icons/fe-alignjustify.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1066 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3055" width="64" height="64"><path d="M170.666667 298.666667h768a42.666667 42.666667 0 1 0 0-85.333334H170.666667a42.666667 42.666667 0 1 0 0 85.333334z m768 426.666666H170.666667a42.666667 42.666667 0 0 0 0 85.333334h768a42.666667 42.666667 0 1 0 0-85.333334z m0-170.666666H170.666667a42.666667 42.666667 0 0 0 0 85.333333h768a42.666667 42.666667 0 1 0 0-85.333333z m0-170.666667H170.666667a42.666667 42.666667 0 1 0 0 85.333333h768a42.666667 42.666667 0 0 0 0-85.333333z" p-id="3056"></path></svg>
|
||||
|
After Width: | Height: | Size: 583 B |
1
frontend/src/icons/fe-bold.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1304" width="64" height="64"><path d="M697.8 481.4c33.6-35 54.2-82.3 54.2-134.3v-10.2C752 229.3 663.9 142 555.3 142H259.4c-15.1 0-27.4 12.3-27.4 27.4v679.1c0 16.3 13.2 29.5 29.5 29.5h318.7c117 0 211.8-94.2 211.8-210.5v-11c0-73-37.4-137.3-94.2-175.1zM328 238h224.7c57.1 0 103.3 44.4 103.3 99.3v9.5c0 54.8-46.3 99.3-103.3 99.3H328V238z m366.6 429.4c0 62.9-51.7 113.9-115.5 113.9H328V542.7h251.1c63.8 0 115.5 51 115.5 113.9v10.8z" p-id="1305"></path></svg>
|
||||
|
After Width: | Height: | Size: 537 B |
1
frontend/src/icons/fe-char.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2418" width="64" height="64"><path d="M792.864 922.112l103.584-2.176L572.576 110.24h-89.184L161.696 919.936H264l66.944-167.936h394.112l67.808 170.112zM369.216 656L528 257.632 686.784 656h-317.568z" p-id="2419"></path></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
1
frontend/src/icons/fe-clear.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2256" width="64" height="64"><path d="M311.769043 997.487304h653.668174v-66.782608H311.769043z" p-id="2257"></path><path d="M638.447304 760.186435l-152.665043-153.355131-152.665044-153.35513L650.245565 134.92313 955.575652 441.633391 638.425043 760.186435z m-173.723826 174.458435h-165.487304L70.77287 717.000348l215.129043-216.086261 18.031304 18.120348L591.187478 807.602087l-126.441739 127.042783z m540.91687-537.6L694.633739 84.658087c-23.752348-23.863652-65.113043-23.774609-88.776348 0L262.28313 429.768348 13.94087 679.201391c-21.147826 21.25913-17.986783 57.677913 7.791304 83.500522L262.906435 992.52313a33.391304 33.391304 0 0 0 22.995478 9.216h192.645565c8.859826 0 17.341217-3.539478 23.596522-9.839304l159.877565-160.567652 343.574261-345.110261A62.73113 62.73113 0 0 0 1024.005565 441.633391c0-16.851478-6.522435-32.678957-18.365217-44.588521z" p-id="2258"></path></svg>
|
||||
|
After Width: | Height: | Size: 968 B |
1
frontend/src/icons/fe-italic.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1619" width="64" height="64"><path d="M798 160H366c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h181.2l-156 544H229c-4.4 0-8 3.6-8 8v64c0 4.4 3.6 8 8 8h432c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8H474.4l156-544H798c4.4 0 8-3.6 8-8v-64c0-4.4-3.6-8-8-8z" p-id="1620"></path></svg>
|
||||
|
After Width: | Height: | Size: 347 B |
1
frontend/src/icons/fe-orderedlist.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2894" width="64" height="64"><path d="M920 760H336c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z m0-568H336c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z m0 284H336c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h584c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM216 712H100c-2.2 0-4 1.8-4 4v34c0 2.2 1.8 4 4 4h72.4v20.5h-35.7c-2.2 0-4 1.8-4 4v34c0 2.2 1.8 4 4 4h35.7V838H100c-2.2 0-4 1.8-4 4v34c0 2.2 1.8 4 4 4h116c2.2 0 4-1.8 4-4V716c0-2.2-1.8-4-4-4zM100 188h38v120c0 2.2 1.8 4 4 4h40c2.2 0 4-1.8 4-4V152c0-4.4-3.6-8-8-8h-78c-2.2 0-4 1.8-4 4v36c0 2.2 1.8 4 4 4z m116 240H100c-2.2 0-4 1.8-4 4v36c0 2.2 1.8 4 4 4h68.4l-70.3 77.7c-1.3 1.5-2.1 3.4-2.1 5.4V592c0 2.2 1.8 4 4 4h116c2.2 0 4-1.8 4-4v-36c0-2.2-1.8-4-4-4h-68.4l70.3-77.7c1.3-1.5 2.1-3.4 2.1-5.4V432c0-2.2-1.8-4-4-4z" p-id="2895"></path></svg>
|
||||
|
After Width: | Height: | Size: 926 B |
1
frontend/src/icons/fe-paragraph.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1147" width="64" height="64"><path d="M149.333333 149.333333h448a42.666667 42.666667 0 1 1 0 85.333334H149.333333a42.666667 42.666667 0 1 1 0-85.333334z m0 640h448a42.666667 42.666667 0 1 1 0 85.333334H149.333333a42.666667 42.666667 0 1 1 0-85.333334z m0-320h725.333334a42.666667 42.666667 0 1 1 0 85.333334H149.333333a42.666667 42.666667 0 1 1 0-85.333334z" p-id="1148"></path></svg>
|
||||
|
After Width: | Height: | Size: 468 B |
1
frontend/src/icons/fe-strike.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2095" width="64" height="64"><path d="M952 474H569.9c-10-2-20.5-4-31.6-6-15.9-2.9-22.2-4.1-30.8-5.8-51.3-10-82.2-20-106.8-34.2-35.1-20.5-52.2-48.3-52.2-85.1 0-37 15.2-67.7 44-89 28.4-21 68.8-32.1 116.8-32.1 54.8 0 97.1 14.4 125.8 42.8 14.6 14.4 25.3 32.1 31.8 52.6 1.3 4.1 2.8 10 4.3 17.8 0.9 4.8 5.2 8.2 9.9 8.2h72.8c5.6 0 10.1-4.6 10.1-10.1v-1c-0.7-6.8-1.3-12.1-2-16-7.3-43.5-28-81.7-59.7-110.3-44.4-40.5-109.7-61.8-188.7-61.8-72.3 0-137.4 18.1-183.3 50.9-25.6 18.4-45.4 41.2-58.6 67.7-13.5 27.1-20.3 58.4-20.3 92.9 0 29.5 5.7 54.5 17.3 76.5 8.3 15.7 19.6 29.5 34.1 42H72c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h433.2c2.1 0.4 3.9 0.8 5.9 1.2 30.9 6.2 49.5 10.4 66.6 15.2 23 6.5 40.6 13.3 55.2 21.5 35.8 20.2 53.3 49.2 53.3 89 0 35.3-15.5 66.8-43.6 88.8-30.5 23.9-75.6 36.4-130.5 36.4-43.7 0-80.7-8.5-110.2-25-29.1-16.3-49.1-39.8-59.7-69.5-0.8-2.2-1.7-5.2-2.7-9-1.2-4.4-5.3-7.5-9.7-7.5h-79.7c-5.6 0-10.1 4.6-10.1 10.1v1c0.2 2.3 0.4 4.2 0.6 5.7 6.5 48.8 30.3 88.8 70.7 118.8 47.1 34.8 113.4 53.2 191.8 53.2 84.2 0 154.8-19.8 204.2-57.3 25-18.9 44.2-42.2 57.1-69 13-27.1 19.7-57.9 19.7-91.5 0-31.8-5.8-58.4-17.8-81.4-5.8-11.2-13.1-21.5-21.8-30.8H952c4.4 0 8-3.6 8-8v-60c0-4.3-3.6-7.9-8-7.9z" p-id="2096"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
frontend/src/icons/fe-underline.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1934" width="64" height="64"><path d="M824 804H200c-4.4 0-8 3.4-8 7.6v60.8c0 4.2 3.6 7.6 8 7.6h624c4.4 0 8-3.4 8-7.6v-60.8c0-4.2-3.6-7.6-8-7.6zM512 728c69.4 0 134.6-27.1 183.8-76.2C745 602.7 772 537.4 772 468V156c0-6.6-5.4-12-12-12h-60c-6.6 0-12 5.4-12 12v312c0 97-79 176-176 176s-176-79-176-176V156c0-6.6-5.4-12-12-12h-60c-6.6 0-12 5.4-12 12v312c0 69.4 27.1 134.6 76.2 183.8C377.3 701 442.6 728 512 728z" p-id="1935"></path></svg>
|
||||
|
After Width: | Height: | Size: 515 B |
1
frontend/src/icons/fe-unorderedlist.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2733" width="64" height="64"><path d="M192 277.333333H106.666667V192h85.333333v85.333333z m725.333333 0H277.333333V192h640v85.333333zM192 554.666667H106.666667v-85.333334h85.333333v85.333334z m725.333333 0H277.333333v-85.333334h640v85.333334zM192 832H106.666667v-85.333333h85.333333v85.333333z m725.333333 0H277.333333v-85.333333h640v85.333333z" p-id="2734"></path></svg>
|
||||
|
After Width: | Height: | Size: 455 B |
1
frontend/src/icons/msg-enter.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3264" width="64" height="64"><path d="M789.333333 128a192 192 0 0 1 191.786667 182.954667L981.333333 320v213.333333a192 192 0 0 1-182.954666 191.786667L789.333333 725.333333H110.976l118.314667 135.253334a21.333333 21.333333 0 0 1 0.682666 27.349333l-2.688 2.773333a21.333333 21.333333 0 0 1-27.306666 0.682667l-2.773334-2.688-149.248-170.624a21.333333 21.333333 0 0 1-3.84-21.76l1.536-3.2 2.304-3.114667 149.333334-170.709333a21.333333 21.333333 0 0 1 34.346666 24.96l-2.261333 3.114667L111.018667 682.666667H789.333333a149.333333 149.333333 0 0 0 149.12-141.141334L938.666667 533.333333v-213.333333a149.333333 149.333333 0 0 0-141.141334-149.12L789.333333 170.666667H512a21.333333 21.333333 0 0 1-3.84-42.325334L512 128h277.333333z" p-id="3265"></path></svg>
|
||||
|
After Width: | Height: | Size: 843 B |
@ -14,8 +14,8 @@ export default {
|
||||
mnx_navToContact() {
|
||||
this.$router.push('/contact')
|
||||
},
|
||||
mnx_navToPdfContentViewer(content_id) {
|
||||
this.$router.push('/pdf-content-viewer/' + content_id)
|
||||
mnx_navToPdfContentViewer(document_id) {
|
||||
this.$router.push('/pdf-content-viewer/' + document_id)
|
||||
},
|
||||
mnx_navToLinkContentViewer(content_link_based64) {
|
||||
this.$router.push('/link-content-viewer/' + content_link_based64)
|
||||
@ -122,6 +122,9 @@ export default {
|
||||
},
|
||||
|
||||
//common
|
||||
mnx_navToLink(path) {
|
||||
this.$router.push('/' + path.replace(/^(\/)+/, ''))
|
||||
},
|
||||
mnx_navAfterSignedin() {
|
||||
if (this.mnx_isUserAuthenticated()) {
|
||||
switch (this.mnx_getUserRole()) {
|
||||
|
||||
@ -12,13 +12,7 @@
|
||||
<div class="form-group">
|
||||
<div class="input-group-container">
|
||||
<div class="form-floating">
|
||||
<input
|
||||
class="input-email"
|
||||
id="inputEmail"
|
||||
type="text"
|
||||
placeholder="name@example.com"
|
||||
v-model="email"
|
||||
/>
|
||||
<input class="input-email" id="inputEmail" type="text" placeholder="name@example.com" v-model="email" />
|
||||
<label for="inputEmail">Email address</label>
|
||||
</div>
|
||||
<button class="btn-start" ref="submitButton" @click="trySigninWithEmail()">
|
||||
@ -33,7 +27,8 @@
|
||||
|
||||
<script>
|
||||
import {
|
||||
UserAuthApi
|
||||
UserAuthApi,
|
||||
applicantValidator
|
||||
// userProfileValidator,
|
||||
} from '../../utils/index'
|
||||
import { signinActionEnum } from '../../types/index'
|
||||
@ -54,11 +49,8 @@ export default {
|
||||
this.message = 'Please type in your email'
|
||||
return
|
||||
}
|
||||
|
||||
console.log('This is the region information', window.location.href)
|
||||
// this.message = userProfileValidator.emailValidator.validate(
|
||||
// this.email
|
||||
// );
|
||||
|
||||
this.message = applicantValidator.emailValidator.validate(this.email)
|
||||
|
||||
if (this.message != null) return
|
||||
|
||||
@ -91,6 +83,7 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
.front-container {
|
||||
padding: 20px 0;
|
||||
|
||||
.slogen {
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
@ -99,6 +92,7 @@ export default {
|
||||
margin-bottom: 24px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.poster {
|
||||
font-size: 48px;
|
||||
font-weight: 500;
|
||||
@ -106,6 +100,7 @@ export default {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
line-height: 52px;
|
||||
|
||||
.blue {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
</div>
|
||||
<button type="submit" class="btn-start">SIGN UP</button>
|
||||
</div>
|
||||
<p class="errorInput" v-if="message != null">{{ message }}</p>
|
||||
<p class="error-msg" v-if="message != null">{{ message }}</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
55
frontend/src/pages/lab/Home.vue
Normal file
@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div class="directories_containter">
|
||||
<div
|
||||
class="directory_container"
|
||||
v-for="(directory, index) in directories"
|
||||
:key="index"
|
||||
@click="view_link(directory)"
|
||||
>
|
||||
<p>{{ directory.title_text }}</p>
|
||||
<img class="directory_cover_image" :src="directory.cover_picture" />
|
||||
<p>{{ directory.summary_text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'LabHome',
|
||||
components: {},
|
||||
computed: {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
view_link(directory) {
|
||||
this.mnx_navToLink(directory.path)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
directories: [
|
||||
{
|
||||
path: 'machine-translation',
|
||||
title_text: 'Machine Translation',
|
||||
summary_text: 'Translate lanuages leverage AI power',
|
||||
icon_picture: '',
|
||||
cover_picture: 'src/assets/images/lab-translation.png'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.directories_containter {
|
||||
@extend .container;
|
||||
}
|
||||
|
||||
.directory_container {
|
||||
@extend .container;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.directory_cover_image {
|
||||
height: 20vh;
|
||||
}
|
||||
</style>
|
||||
45
frontend/src/pages/lab/translation/Home.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="input_containter">
|
||||
<input class="input_text" type="text" v-model="input_text" @keyup.enter="translate($event)" />
|
||||
<p class="translated_text">{{ translated_text }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { LabApi } from '../../../utils/index'
|
||||
export default {
|
||||
name: 'TranslationHome',
|
||||
components: {},
|
||||
computed: {},
|
||||
mounted() {},
|
||||
methods: {
|
||||
translate($event) {
|
||||
LabApi.translate_text(this.input_text)
|
||||
.then((response) => {
|
||||
this.translated_text = response.data
|
||||
})
|
||||
.catch((error) => {
|
||||
this.mnx_backendErrorHandler(error)
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
input_text: null,
|
||||
translated_text: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.input_containter {
|
||||
@extend .container;
|
||||
@extend .m-3;
|
||||
}
|
||||
.input_text {
|
||||
@extend .w-100;
|
||||
}
|
||||
.translated_text {
|
||||
@extend .w-100;
|
||||
}
|
||||
</style>
|
||||
@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div style="width: 100vw; height: 100vh">
|
||||
<PDFReader document="6662431ee312d5389c612020" />
|
||||
<div class="pdf-viewer">
|
||||
<PDFReader :doc="content_media_data" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// import { pdfjsLib } from '../../plugins/index'
|
||||
// import { ContentApi } from '../../utils/index'
|
||||
// import { Buffer } from 'buffer'
|
||||
import { ContentApi } from '@/utils/index'
|
||||
import PDFReader from '@/components/PDFReader.vue'
|
||||
export default {
|
||||
name: 'PdfContentViewer',
|
||||
@ -19,29 +20,14 @@ export default {
|
||||
components: { PDFReader },
|
||||
computed: {},
|
||||
mounted() {
|
||||
// this.retrieve_blog_content()
|
||||
this.retrieve_blog_content(this.content_id)
|
||||
},
|
||||
methods: {
|
||||
// retrieve_blog_content() {
|
||||
// ContentApi.retrieve_blog_content(this.content_id)
|
||||
// .then((response) => {
|
||||
// this.content_media_data = response.data
|
||||
// var loadingTask = pdfjsLib.getDocument({
|
||||
// data: Buffer.from(this.content_media_data, 'base64')
|
||||
// })
|
||||
// loadingTask.promise.then(function (pdf) {
|
||||
// //
|
||||
// // Fetch the first page
|
||||
// //
|
||||
// pdf.getPage(1).then(function (page) {
|
||||
// //rendering
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// this.mnx_backendErrorHandler(error)
|
||||
// })
|
||||
// }
|
||||
retrieve_blog_content(document_id) {
|
||||
ContentApi.retrieve_blog_content(document_id).then((response) => {
|
||||
this.content_media_data = { url: response.data }
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -50,3 +36,6 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
// .pdf-viewer {height: $body-height;}
|
||||
</style>
|
||||
|
||||
@ -1,81 +1,87 @@
|
||||
<template>
|
||||
<div class="message-hub-conainter">
|
||||
<div class="conversation-list-container">
|
||||
<div
|
||||
v-for="(conversation, index) in conversations"
|
||||
:key="index"
|
||||
class="conversation-container"
|
||||
@click="selectConversation(conversation)"
|
||||
>
|
||||
<div class="participant-portrait-container">
|
||||
<img
|
||||
class="participant-portrait"
|
||||
alt="user portrait"
|
||||
src="@/assets/images/default-user-portrait.png"
|
||||
/>
|
||||
</div>
|
||||
<div class="conversation-summary-container">
|
||||
<div class="conversation-summary-header-container">
|
||||
<span class="participant-fullname">
|
||||
{{ conversation.summary.contact.first_name }}
|
||||
{{ conversation.summary.contact.last_name }}
|
||||
</span>
|
||||
<span class="conversation-last-update-date">
|
||||
{{ getDateFromFulltimeString(conversation.summary.last_message.create_time) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="conversation-summary-highlight-container">
|
||||
<span class="conversation-last-message-summary">{{
|
||||
conversation.summary.last_message.message_body
|
||||
}}</span>
|
||||
<div class="message-container">
|
||||
<div class="message-hub-conainter">
|
||||
<div v-if="conversations && conversations.length > 0" class="conversation-list-container">
|
||||
<div
|
||||
v-for="(conversation, index) in conversations"
|
||||
:key="index"
|
||||
class="conversation-container"
|
||||
:class="{
|
||||
selected:
|
||||
current_thread?.conversation?.information?.conversation_id ===
|
||||
conversation.summary.last_message.conversation_id
|
||||
}"
|
||||
@click="selectConversation(conversation)"
|
||||
>
|
||||
<img class="participant-portrait" alt="user portrait" src="@/assets/profile.png" />
|
||||
<div class="conversation-summary-container">
|
||||
<div class="conversation-summary-header-container">
|
||||
<span class="participant-fullname">
|
||||
{{ conversation.summary.contact.first_name }}
|
||||
{{ conversation.summary.contact.last_name }}
|
||||
</span>
|
||||
<span class="conversation-last-update-date">{{
|
||||
getDateFromFulltimeString(conversation.summary.last_message.create_time)
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="conversation-summary-highlight-container">
|
||||
{{ conversation.summary.last_message.message_body }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="message-panel-container">
|
||||
<div v-if="current_thread" class="message-thread-container">
|
||||
<div
|
||||
v-for="(item, index) in current_thread.conversation.messages"
|
||||
:key="index"
|
||||
class="message-item-container"
|
||||
>
|
||||
<div class="message-item-header-container">
|
||||
<div class="message-item-sender-info">
|
||||
<div
|
||||
v-if="!conversations || conversations.length == 0"
|
||||
class="conversation-list-empty-container"
|
||||
>
|
||||
Empty conversation
|
||||
</div>
|
||||
<div class="message-panel-container">
|
||||
<div v-if="current_thread" class="message-thread-container">
|
||||
<div
|
||||
v-for="(item, index) in current_thread.conversation.messages"
|
||||
:key="index"
|
||||
class="message-item-container"
|
||||
:class="item.raw_data.sender_id == userIdentityNote ? 'me' : ''"
|
||||
>
|
||||
<div class="message-item-header-container">
|
||||
<img
|
||||
class="message-item-sender-portrait"
|
||||
alt="user portrait"
|
||||
src="@/assets/images/default-user-portrait.png"
|
||||
src="@/assets/profile.png"
|
||||
/>
|
||||
<span class="message-item-sender-fullname">
|
||||
{{ item.sender_profile.first_name }}
|
||||
{{ item.sender_profile.last_name }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="message-item-additional-info-container">
|
||||
<span class="message-item-create-time">
|
||||
{{ getDateFromFulltimeString(item.raw_data.create_time) }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="message-item-message-body-container">
|
||||
<p class="message-item-message-body-span">{{ item.raw_data.message_body }}</p>
|
||||
<div class="message-item-message-body">{{ item.raw_data.message_body }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="message-writing-panel-container">
|
||||
<input
|
||||
class="writing-message-input"
|
||||
type="text"
|
||||
v-model="writtenMessage"
|
||||
@keypress.enter="sendMessage(current_thread.conversation.information.conversation_id)"
|
||||
/>
|
||||
<div v-if="!current_thread" class="message-thread-empty-container">
|
||||
Please choose conversation
|
||||
</div>
|
||||
<div class="message-writing-panel-container">
|
||||
<svg-icon icon="msg-enter" class-name="writing-message-enter" />
|
||||
<input
|
||||
class="writing-message-input"
|
||||
type="text"
|
||||
v-model="writtenMessage"
|
||||
@keypress.enter="sendMessage(current_thread.conversation.information.conversation_id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import SvgIcon from '../../../components/SvgIcon.vue'
|
||||
import { MessageHubApi, DateUtils } from '../../../utils/index'
|
||||
export default {
|
||||
components: { SvgIcon },
|
||||
name: 'MessageHub',
|
||||
props: {},
|
||||
mounted() {
|
||||
@ -83,7 +89,7 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
conversations: {
|
||||
handler: function (val, oldVal) {
|
||||
handler: function (val) {
|
||||
if (val && val.length > 0) {
|
||||
this.current_thread = val[0]
|
||||
}
|
||||
@ -93,6 +99,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userIdentityNote: this.mnx_getUserIdentity(),
|
||||
conversations: [],
|
||||
current_thread: null,
|
||||
writtenMessage: null
|
||||
@ -124,145 +131,204 @@ export default {
|
||||
})
|
||||
},
|
||||
getDateFromFulltimeString(fulltime) {
|
||||
return DateUtils.FromJsonToDatetimeString(fulltime)
|
||||
return DateUtils.FromJsonToHMDateString(fulltime)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.message-hub-conainter {
|
||||
@extend .flex-row-container;
|
||||
@extend .justify-content-between;
|
||||
@extend .border;
|
||||
@extend .w-100;
|
||||
}
|
||||
.message-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: $body-height;
|
||||
padding: 24px 24px 0 24px;
|
||||
justify-content: center;
|
||||
.message-hub-conainter {
|
||||
display: flex;
|
||||
box-shadow: 0px 0px 24px 0px #d4d3e380;
|
||||
border-radius: 12px;
|
||||
width: 100%;
|
||||
max-width: $body-width;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
.conversation-list-container {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
width: 300px;
|
||||
.conversation-container {
|
||||
background-color: #f3f3f5;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
height: 98px;
|
||||
padding: 18px;
|
||||
.participant-portrait {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 18px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
.conversation-summary-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
.conversation-summary-header-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
color: #0d1637;
|
||||
.participant-fullname {
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.conversation-last-update-date {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
.conversation-summary-highlight-container {
|
||||
font-size: 14px;
|
||||
color: #6e7387;
|
||||
white-space: normal;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
opacity: 0.78;
|
||||
}
|
||||
&.selected {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
.conversation-list-empty-container {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
width: 300px;
|
||||
background-color: #f3f3f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.message-panel-container {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
padding-bottom: 98px;
|
||||
position: relative;
|
||||
.message-thread-container {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 20px 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.message-item-container {
|
||||
max-width: 70%;
|
||||
margin-bottom: 15px;
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.message-item-header-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 18px;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
.message-item-sender-portrait {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
.message-item-sender-fullname {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: #16181e;
|
||||
font-weight: 500;
|
||||
margin: 0 7px;
|
||||
}
|
||||
.message-item-create-time {
|
||||
color: #737478;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
.message-item-message-body {
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
color: #0d1637;
|
||||
background-color: #f3f3f5;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
text-align: left;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.conversation-list-container {
|
||||
@extend .flex-colum-container;
|
||||
@extend .justify-content-start;
|
||||
@extend .w-40;
|
||||
@extend .m-1;
|
||||
@extend .border;
|
||||
min-height: 80vh;
|
||||
}
|
||||
|
||||
.conversation-container {
|
||||
@extend .flex-row-container;
|
||||
@extend .justify-content-between;
|
||||
@extend .border;
|
||||
@extend .w-100;
|
||||
@extend .my-3;
|
||||
@extend .focus-ring;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.participant-portrait-container {
|
||||
@extend .float-start;
|
||||
}
|
||||
|
||||
.participant-portrait {
|
||||
@extend .img-fluid;
|
||||
}
|
||||
|
||||
.conversation-summary-container {
|
||||
@extend .flex-colum-container;
|
||||
@extend .w-90;
|
||||
}
|
||||
|
||||
.conversation-summary-header-container {
|
||||
@extend .flex-row-container;
|
||||
@extend .justify-content-between;
|
||||
@extend .w-100;
|
||||
}
|
||||
|
||||
.participant-fullname {
|
||||
@extend .text-start;
|
||||
@extend .label-text-light;
|
||||
}
|
||||
|
||||
.conversation-last-update-date {
|
||||
@extend .text-start;
|
||||
@extend .label-text-light;
|
||||
}
|
||||
|
||||
.conversation-summary-highlight-container {
|
||||
@extend .container;
|
||||
}
|
||||
|
||||
.conversation-last-message-summary {
|
||||
@extend .d-inline-block;
|
||||
@extend .text-truncate;
|
||||
@extend .w-100;
|
||||
}
|
||||
|
||||
.message-panel-container {
|
||||
@extend .flex-colum-container;
|
||||
@extend .w-60;
|
||||
@extend .m-1;
|
||||
@extend .border;
|
||||
@extend .justify-content-between;
|
||||
min-height: 80vh;
|
||||
}
|
||||
|
||||
.message-thread-container {
|
||||
@extend .flex-colum-container;
|
||||
@extend .m-1;
|
||||
@extend .border;
|
||||
@extend .align-items-start;
|
||||
@extend .flex-grow-1;
|
||||
}
|
||||
|
||||
.message-item-container {
|
||||
@extend .flex-colum-container;
|
||||
@extend .m-1;
|
||||
}
|
||||
|
||||
.message-item-header-container {
|
||||
@extend .flex-row-container;
|
||||
@extend .justify-content-between;
|
||||
@extend .w-100;
|
||||
}
|
||||
|
||||
.message-item-sender-info {
|
||||
@extend .m-1;
|
||||
}
|
||||
|
||||
.message-item-additional-info-container {
|
||||
@extend .m-1;
|
||||
}
|
||||
|
||||
.message-item-sender-portrait {
|
||||
@extend .img-fluid;
|
||||
height: 2vh;
|
||||
}
|
||||
|
||||
.message-item-sender-fullname {
|
||||
@extend .label-text;
|
||||
@extend .mx-1;
|
||||
}
|
||||
|
||||
.message-item-create-time {
|
||||
@extend .label-text;
|
||||
@extend .mx-1;
|
||||
}
|
||||
|
||||
.message-item-message-body-container {
|
||||
@extend .container;
|
||||
}
|
||||
|
||||
.message-item-message-body-span {
|
||||
@extend .text-start;
|
||||
}
|
||||
|
||||
.message-writing-panel-container {
|
||||
@extend .flex-colum-container;
|
||||
@extend .border;
|
||||
min-height: 10vh;
|
||||
}
|
||||
|
||||
.writing-message-input {
|
||||
@extend .text-start;
|
||||
@extend .w-100;
|
||||
@extend .flex-grow-1;
|
||||
&.me {
|
||||
align-self: flex-end;
|
||||
.message-item-header-container {
|
||||
flex-direction: row-reverse;
|
||||
align-self: flex-end;
|
||||
}
|
||||
.message-item-message-body {
|
||||
background-color: $primary;
|
||||
color: white;
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.message-thread-empty-container {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 20px 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.message-writing-panel-container {
|
||||
height: 98px;
|
||||
padding: 24px;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
.writing-message-enter {
|
||||
position: absolute;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
background-color: #f3f3f5;
|
||||
padding: 7px;
|
||||
color: #9ea2af;
|
||||
top: 37px;
|
||||
right: 37px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
.writing-message-input {
|
||||
height: 50px;
|
||||
border: 1px solid #e1e1e1;
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
padding: 0 48px 0 12px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,267 +1,254 @@
|
||||
<template>
|
||||
<div class="provider-hub-container">
|
||||
<div v-if="recommendedProviders" class="accordion" id="provider-accordion-container">
|
||||
<div class="provider-hub-container" id="provider-accordion-container">
|
||||
<template v-if="recommendedProviders">
|
||||
<div
|
||||
class="accordion accordion-list"
|
||||
v-for="(provider, index) in recommendedProviders"
|
||||
:key="index"
|
||||
class="accordion-item my-3"
|
||||
>
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
:data-bs-target="'#collapse' + index"
|
||||
aria-expanded="false"
|
||||
:aria-controls="'collapse' + index"
|
||||
<div class="accordion-item my-3">
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
:data-bs-target="'#collapse' + index"
|
||||
aria-expanded="false"
|
||||
:aria-controls="'collapse' + index"
|
||||
>
|
||||
<div class="provider-summary-containter dashed-container">
|
||||
<div class="provider-portrait-containter">
|
||||
<img class="provider-portrait" alt="user portrait" src="@/assets/profile.png" />
|
||||
</div>
|
||||
<div class="provider-name-container">
|
||||
<label class="provider-name-label" for="provider-name">Name</label>
|
||||
<span class="provider-name-span" id="provider-name">
|
||||
{{ provider.user_profile.first_name }}
|
||||
{{ provider.user_profile.last_name }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="provider-stay-on-freeleaps-container">
|
||||
<label class="provider-stay-on-freeleaps-label" for="provider-stay-on-freeleaps"
|
||||
>Stay on Freeleaps</label
|
||||
>
|
||||
<span class="provider-stay-on-freeleaps-span" id="provider-stay-on-freeleaps">
|
||||
{{ provider.activeness_achievement.days_of_staying_on }} day(s)</span
|
||||
>
|
||||
</div>
|
||||
<div class="provider-delivered-projects-container">
|
||||
<label class="provider-delivered-projects-label" for="provider-delivered-projects"
|
||||
>Delivered projects</label
|
||||
>
|
||||
<span class="provider-delivered-projects-span" id="provider-delivered-projects">
|
||||
{{ provider.provider_achievement.delivered_projects }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="provider-responding-time-container">
|
||||
<label class="provider-responding-time-label" for="provider-responding-time"
|
||||
>Responding time</label
|
||||
>
|
||||
<span class="provider-responding-time-span" id="provider-responding-time">
|
||||
{{ provider.provider_achievement.responding_time_in_minutes }} min(s)
|
||||
</span>
|
||||
</div>
|
||||
<div class="provider-credit-score-container">
|
||||
<label class="provider-credit-score-label" for="provider-credit-score"
|
||||
>Credit score</label
|
||||
>
|
||||
<span class="provider-credit-score-span" id="provider-credit-score">
|
||||
{{ provider.provider_achievement.credit }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
:id="'collapse' + index"
|
||||
class="accordion-collapse collapse"
|
||||
data-bs-parent="#provider-accordion-container"
|
||||
>
|
||||
<div class="provider-summary-containter">
|
||||
<div class="provider-portrait-containter">
|
||||
<img
|
||||
class="provider-portrait"
|
||||
alt="user portrait"
|
||||
src="@/assets/images/default-user-portrait.png"
|
||||
/>
|
||||
<div class="accordion-body">
|
||||
<div class="self-intro-container">
|
||||
<label class="self-intro-content-label" for="self-intro-content">Self intro</label>
|
||||
<div
|
||||
class="self-intro-content-container"
|
||||
id="self-intro-content"
|
||||
v-html="provider.user_profile.self_intro.content_html"
|
||||
></div>
|
||||
</div>
|
||||
<div class="provider-name-container">
|
||||
<label class="provider-name-label" for="provider-name">Name</label>
|
||||
<span class="provider-name-span" id="provider-name">
|
||||
{{ provider.user_profile.first_name }} {{ provider.user_profile.last_name }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="provider-stay-on-freeleaps-container">
|
||||
<label class="provider-stay-on-freeleaps-label" for="provider-stay-on-freeleaps"
|
||||
>Stay on Freeleaps</label
|
||||
>
|
||||
<span class="provider-stay-on-freeleaps-span" id="provider-stay-on-freeleaps">
|
||||
{{ provider.activeness_achievement.days_of_staying_on }} day(s)</span
|
||||
>
|
||||
</div>
|
||||
<div class="provider-delivered-projects-container">
|
||||
<label class="provider-delivered-projects-label" for="provider-delivered-projects"
|
||||
>Delivered projects</label
|
||||
>
|
||||
<span class="provider-delivered-projects-span" id="provider-delivered-projects">
|
||||
{{ provider.provider_achievement.delivered_projects }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="provider-responding-time-container">
|
||||
<label class="provider-responding-time-label" for="provider-responding-time"
|
||||
>Responding time</label
|
||||
>
|
||||
<span class="provider-responding-time-span" id="provider-responding-time">
|
||||
{{ provider.provider_achievement.responding_time_in_minutes }} min(s)
|
||||
</span>
|
||||
</div>
|
||||
<div class="provider-credit-score-container">
|
||||
<label class="provider-credit-score-label" for="provider-credit-score"
|
||||
>Credit score</label
|
||||
>
|
||||
<span class="provider-credit-score-span" id="provider-credit-score">
|
||||
{{ provider.provider_achievement.credit }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
:id="'collapse' + index"
|
||||
class="accordion-collapse collapse"
|
||||
data-bs-parent="#provider-accordion-container"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<div class="self-intro-container">
|
||||
<label class="self-intro-content-label" for="self-intro-content">Self intro</label>
|
||||
<div
|
||||
class="self-intro-content-container"
|
||||
id="self-intro-content"
|
||||
v-html="provider.user_profile.self_intro.content_html"
|
||||
></div>
|
||||
</div>
|
||||
<div class="statistics-container">
|
||||
<label class="statistics-content-label" for="statistics-content">Profile</label>
|
||||
<div class="statistics-content-container" id="statistics-content">
|
||||
<div class="delivery-container">
|
||||
<div class="delivery-delivered-project-container">
|
||||
<label
|
||||
class="delivery-delivered-projects-label"
|
||||
for="delivery-delivered-projects"
|
||||
>Delivered projects</label
|
||||
>
|
||||
<span class="delivery-delivered-projects-span" id="delivery-delivered-projects">
|
||||
{{ provider.provider_achievement.delivered_projects }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="delivery-time-per-project-container">
|
||||
<label class="delivery-time-per-project-label" for="delivery-time-per-project"
|
||||
>Project delivering time</label
|
||||
>
|
||||
<span class="delivery-time-per-project-span" id="delivery-time-per-project">
|
||||
{{ provider.provider_deliveries.delivering_time_per_project_in_day }} day(s)
|
||||
</span>
|
||||
</div>
|
||||
<div class="delivery-top-programming-language-container">
|
||||
<label
|
||||
class="delivery-top-programming-language-label"
|
||||
for="delivery-top-programming-language"
|
||||
>Top programming languages</label
|
||||
>
|
||||
<div
|
||||
class="delivery-top-programming-language-content-container"
|
||||
id="delivery-top-programming-language"
|
||||
>
|
||||
<span
|
||||
v-for="(lang, index) in provider.provider_deliveries
|
||||
.top_programming_languages"
|
||||
:key="index"
|
||||
class="delivery-top-programming-language-span"
|
||||
<div class="statistics-container">
|
||||
<label class="self-intro-content-label" for="statistics-content">Profile</label>
|
||||
<div class="statistics-content-container" id="statistics-content">
|
||||
<div class="delivery-container">
|
||||
<div class="dd-project-container">
|
||||
<label class="dd-project-label" for="delivery-delivered-projects"
|
||||
>Delivered projects</label
|
||||
>
|
||||
#{{ lang }}
|
||||
<span class="dd-project-span" id="delivery-delivered-projects">
|
||||
{{ provider.provider_achievement.delivered_projects }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="dd-project-container">
|
||||
<label class="dd-project-label" for="delivery-time-per-project"
|
||||
>Project delivering time</label
|
||||
>
|
||||
<span class="dd-project-span" id="delivery-time-per-project">
|
||||
{{ provider.provider_deliveries.delivering_time_per_project_in_day }} day(s)
|
||||
</span>
|
||||
</div>
|
||||
<div class="dd-project-container">
|
||||
<label class="dd-project-label" for="delivery-top-programming-language"
|
||||
>Top programming languages</label
|
||||
>
|
||||
<div
|
||||
class="delivery-top-programming-language-content-container"
|
||||
id="delivery-top-programming-language"
|
||||
>
|
||||
<span
|
||||
v-for="(lang, index) in provider.provider_deliveries
|
||||
.top_programming_languages"
|
||||
:key="index"
|
||||
class="dd-project-span"
|
||||
>
|
||||
#{{ lang }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dd-project-container">
|
||||
<label class="dd-project-label" for="delivery-weekly-produced-code"
|
||||
>Weekly produced code</label
|
||||
>
|
||||
<span class="dd-project-span" id="delivery-weekly-produced-code">
|
||||
{{ provider.provider_deliveries.lines_of_code_per_week }} line(s)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="delivery-weekly-produced-code-container">
|
||||
<label
|
||||
class="delivery-weekly-produced-code-label"
|
||||
for="delivery-weekly-produced-code"
|
||||
>Weekly produced code</label
|
||||
>
|
||||
<span
|
||||
class="delivery-weekly-produced-code-span"
|
||||
id="delivery-weekly-produced-code"
|
||||
>
|
||||
{{ provider.provider_deliveries.lines_of_code_per_week }} line(s)
|
||||
</span>
|
||||
<div class="delivery-container">
|
||||
<div class="dd-project-container">
|
||||
<label class="dd-project-label" for="activity-ongoing-projects"
|
||||
>Ongoing projects</label
|
||||
>
|
||||
<span class="dd-project-span" id="activity-ongoing-projects">
|
||||
{{ provider.provider_activities.ongoing_projects }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="dd-project-container">
|
||||
<label class="dd-project-label" for="activity-invitation-to-requests"
|
||||
>Invitations to requests</label
|
||||
>
|
||||
<span class="dd-project-span" id="activity-invitation-to-requests">
|
||||
{{ provider.provider_activities.invitations_to_open_requests }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="dd-project-container">
|
||||
<label class="dd-project-label" for="activity-active-proposals"
|
||||
>Active proposals</label
|
||||
>
|
||||
<span class="dd-project-span" id="activity-active-proposals">
|
||||
{{ provider.provider_activities.active_proposals }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="dd-project-container">
|
||||
<label class="dd-project-label" for="activity-hourly-rate"
|
||||
>Expected hourly rate</label
|
||||
>
|
||||
<span class="dd-project-span" id="activity-hourly-rate">
|
||||
{{ provider.provider_profile.expected_salary.hourly }}
|
||||
{{ provider.provider_profile.expected_salary.currency }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="activity-container">
|
||||
<div class="activity-ongoing-projects-container">
|
||||
<label class="activity-ongoing-projects-label" for="activity-ongoing-projects"
|
||||
>Ongoing projects</label
|
||||
>
|
||||
<span class="activity-ongoing-projects-span" id="activity-ongoing-projects">
|
||||
{{ provider.provider_activities.ongoing_projects }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="activity-invitation-to-requests-container">
|
||||
<label
|
||||
class="activity-invitation-to-requests-label"
|
||||
for="activity-invitation-to-requests"
|
||||
>Invitations to requests</label
|
||||
>
|
||||
<span
|
||||
class="activity-invitation-to-requests-span"
|
||||
id="activity-invitation-to-requests"
|
||||
>
|
||||
{{ provider.provider_activities.invitations_to_open_requests }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="activity-active-proposals-container">
|
||||
<label class="activity-active-proposals-label" for="activity-active-proposals"
|
||||
>Active proposals</label
|
||||
>
|
||||
<span class="activity-active-proposals-span" id="activity-active-proposals">
|
||||
{{ provider.provider_activities.active_proposals }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="delivery-hourly-rate-container">
|
||||
<label class="delivery-hourly-rate-label" for="activity-hourly-rate"
|
||||
>Expected hourly rate</label
|
||||
>
|
||||
<span class="delivery-hourly-rate-span" id="activity-hourly-rate">
|
||||
{{ provider.provider_profile.expected_salary.hourly }}
|
||||
{{ provider.provider_profile.expected_salary.currency }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="work-quality-container">
|
||||
<div class="quality-code-issue-rate-container">
|
||||
<label class="quality-code-issue-rate-label" for="quality-issue-rate"
|
||||
>Code issue rate</label
|
||||
>
|
||||
<span class="quality-code-issue-rate-span" id="quality-issue-rate">
|
||||
{{ provider.provider_work_quality.issues_per_thousand_lines_of_codes }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="quality-online-issue-rate-container">
|
||||
<label class="quality-online-issue-rate-label" for="quality-online-issue-rate"
|
||||
>Online issue rate</label
|
||||
>
|
||||
<span class="quality-online-issue-rate-span" id="quality-online-issue-rate">
|
||||
{{ provider.provider_work_quality.issues_after_delivery_per_project }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="quality-issue-fixing-rate-container">
|
||||
<label class="quality-issue-fixing-rate-label" for="quality-issue-fixing-rate"
|
||||
>Issue fixing rate</label
|
||||
>
|
||||
<span class="quality-issue-fixing-rate-span" id="quality-issue-fixing-rate">
|
||||
{{ provider.provider_work_quality.issue_fixing_rate_pencentage }}%
|
||||
</span>
|
||||
</div>
|
||||
<div class="quality-issue-fixing-time-container">
|
||||
<label class="quality-issue-fixing-time-label" for="quality-issue-fixing-time"
|
||||
>Issue fixing time</label
|
||||
>
|
||||
<span class="quality-issue-fixing-time-span" id="quality-issue-fixing-time">
|
||||
{{ provider.provider_work_quality.issue_fixing_time_minutes }} min(s)
|
||||
</span>
|
||||
<div class="delivery-container">
|
||||
<div class="dd-project-container">
|
||||
<label class="dd-project-label" for="quality-issue-rate"
|
||||
>Code issue rate</label
|
||||
>
|
||||
<span class="dd-project-span" id="quality-issue-rate">
|
||||
{{ provider.provider_work_quality.issues_per_thousand_lines_of_codes }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="dd-project-container">
|
||||
<label class="dd-project-label" for="quality-online-issue-rate"
|
||||
>Online issue rate</label
|
||||
>
|
||||
<span class="dd-project-span" id="quality-online-issue-rate">
|
||||
{{ provider.provider_work_quality.issues_after_delivery_per_project }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="dd-project-container">
|
||||
<label class="dd-project-label" for="quality-issue-fixing-rate"
|
||||
>Issue fixing rate</label
|
||||
>
|
||||
<span class="dd-project-span" id="quality-issue-fixing-rate">
|
||||
{{ provider.provider_work_quality.issue_fixing_rate_pencentage }}%
|
||||
</span>
|
||||
</div>
|
||||
<div class="dd-project-container">
|
||||
<label class="dd-project-label" for="quality-issue-fixing-time"
|
||||
>Issue fixing time</label
|
||||
>
|
||||
<span class="dd-project-span" id="quality-issue-fixing-time">
|
||||
{{ provider.provider_work_quality.issue_fixing_time_minutes }} min(s)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="requests" class="accordion" id="accordion-action-panel">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
class="accordion-button"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapse-action-panel"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapse-action-panel"
|
||||
<div v-if="requests" class="accordion" id="accordion-action-panel">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
class="accordion-button"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapse-action-panel"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapse-action-panel"
|
||||
>
|
||||
Action panel
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
id="collapse-action-panel"
|
||||
class="accordion-collapse collapse"
|
||||
data-bs-parent="#accordion-action-panel"
|
||||
>
|
||||
Action panel
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
id="collapse-action-panel"
|
||||
class="accordion-collapse collapse"
|
||||
data-bs-parent="#accordion-action-panel"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<div class="invite-to-request-container">
|
||||
<label class="invite-to-request-content-label" for="invite-to-request-content"
|
||||
>Invite
|
||||
<span class="invite-to-request-name-span"
|
||||
>{{ provider.user_profile.first_name }}
|
||||
{{ provider.user_profile.last_name }}</span
|
||||
<div class="accordion-body">
|
||||
<div class="invite-to-request-container">
|
||||
<label
|
||||
class="invite-to-request-content-label"
|
||||
for="invite-to-request-content"
|
||||
>Invite
|
||||
<span class="invite-to-request-name-span"
|
||||
>{{ provider.user_profile.first_name }}
|
||||
{{ provider.user_profile.last_name }}</span
|
||||
>
|
||||
to my open requests</label
|
||||
>
|
||||
to my open requests</label
|
||||
>
|
||||
<div
|
||||
class="invite-to-request-content-container"
|
||||
id="invite-to-request-content"
|
||||
>
|
||||
<div class="form-check" v-for="(request, index) in requests" :key="index">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
:value="request.id"
|
||||
:id="'check' + request.id"
|
||||
v-model="checkedRequests"
|
||||
@change="
|
||||
checkRequest($event, request.id, provider.user_profile.user_id)
|
||||
"
|
||||
/>
|
||||
<label class="form-check-label" :for="'check' + request.id">{{
|
||||
request.title
|
||||
}}</label>
|
||||
<div
|
||||
class="invite-to-request-content-container"
|
||||
id="invite-to-request-content"
|
||||
>
|
||||
<div class="form-check" v-for="(request, index) in requests" :key="index">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
:value="request.id"
|
||||
:id="'check' + request.id"
|
||||
v-model="checkedRequests"
|
||||
@change="
|
||||
checkRequest($event, request.id, provider.user_profile.user_id)
|
||||
"
|
||||
/>
|
||||
<label class="form-check-label" :for="'check' + request.id">{{
|
||||
request.title
|
||||
}}</label>
|
||||
</div>
|
||||
<span class="invite-to-request-note-text"
|
||||
>*Once the request is selected, the provider will be invited to see the
|
||||
request.</span
|
||||
>
|
||||
</div>
|
||||
<span class="invite-to-request-note-text"
|
||||
>*Once the request is selected, the provider will be invited to see the
|
||||
request.</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -271,7 +258,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -422,13 +409,16 @@ export default {
|
||||
|
||||
.self-intro-container {
|
||||
@extend .flex-colum-container;
|
||||
@extend .m-1;
|
||||
@extend .border;
|
||||
margin-bottom: 20px;
|
||||
// @extend .m-1;
|
||||
// @extend .border;
|
||||
}
|
||||
|
||||
.self-intro-content-label {
|
||||
@extend .label-text-light;
|
||||
@extend .w-100;
|
||||
font-size: 14px;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.self-intro-content-container {
|
||||
@ -439,43 +429,45 @@ export default {
|
||||
|
||||
.statistics-container {
|
||||
@extend .flex-colum-container;
|
||||
@extend .m-1;
|
||||
@extend .border;
|
||||
margin-bottom: 20px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.statistics-content-label {
|
||||
@extend .label-text-light;
|
||||
@extend .w-100;
|
||||
}
|
||||
// .statistics-content-label {
|
||||
// @extend .label-text-light;
|
||||
// @extend .w-100;
|
||||
// }
|
||||
|
||||
.statistics-content-container {
|
||||
@extend .flex-colum-container;
|
||||
@extend .m-1;
|
||||
padding: 0;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.delivery-container {
|
||||
@extend .flex-row-container;
|
||||
@extend .justify-content-around;
|
||||
@extend .m-1;
|
||||
@extend .border;
|
||||
@extend .w-100;
|
||||
background-color: rgba(32, 90, 239, 0.03);
|
||||
margin-bottom: 12px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.delivery-delivered-project-container {
|
||||
.dd-project-container {
|
||||
@extend .flex-colum-container;
|
||||
@extend .w-20;
|
||||
@extend .m-1;
|
||||
}
|
||||
|
||||
.delivery-delivered-projects-label {
|
||||
.dd-project-label {
|
||||
@extend .label-text-light;
|
||||
@extend .w-100;
|
||||
}
|
||||
|
||||
.delivery-delivered-projects-span {
|
||||
.dd-project-span {
|
||||
@extend .text-start;
|
||||
@extend .w-90;
|
||||
@extend .m-1;
|
||||
@extend .w-100;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.delivery-time-per-project-container {
|
||||
@ -526,7 +518,7 @@ export default {
|
||||
.delivery-top-programming-language-content-container {
|
||||
@extend .flex-row-container;
|
||||
@extend .justify-content-start;
|
||||
@extend .m-1;
|
||||
padding: 0;
|
||||
@extend .w-100;
|
||||
}
|
||||
|
||||
|
||||
@ -6,157 +6,167 @@
|
||||
class="request-invitations"
|
||||
:id="group.name"
|
||||
>
|
||||
<div v-if="group.data" class="accordion" id="request-invitation-container">
|
||||
<div v-if="group.data" id="request-invitation-container">
|
||||
<div
|
||||
class="accordion accordion-list"
|
||||
v-for="(request, index) in group.data"
|
||||
:key="index"
|
||||
:id="request.id"
|
||||
class="accordion-item my-3"
|
||||
>
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
:data-bs-target="'#collapse' + index"
|
||||
aria-expanded="false"
|
||||
:aria-controls="'collapse' + index"
|
||||
>
|
||||
<div class="request-content-container">
|
||||
<div class="request-content-issuer-container">
|
||||
<label class="request-content-label" for="request-content-issuer-box"
|
||||
>Issuer</label
|
||||
>
|
||||
<div class="request-content-box" id="request-content-issuer-box">
|
||||
<span class="request-content-issuer-text">
|
||||
{{ request.issuer_profile.first_name }}
|
||||
{{ request.issuer_profile.last_name }}</span
|
||||
<div class="accordion-item my-3">
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
:data-bs-target="'#collapse' + index"
|
||||
aria-expanded="false"
|
||||
:aria-controls="'collapse' + index"
|
||||
>
|
||||
<div class="dashed-container request-content-container">
|
||||
<div class="request-content-issuer-container">
|
||||
<label class="request-content-label" for="request-content-issuer-box"
|
||||
>Issuer</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="request-content-title-container">
|
||||
<label class="request-content-label" for="request-content-title-box"
|
||||
>Request</label
|
||||
>
|
||||
<div class="request-content-box" id="request-content-title-box">
|
||||
<span class="request-content-title-text"> {{ request.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="request-content-date-container">
|
||||
<label class="request-content-label" for="request-content-date-box">Date</label>
|
||||
<div class="request-content-box" id="request-content-date-box">
|
||||
<span class="request-content-date-text">
|
||||
{{ getDateFromFulltimeString(request.update_time) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="request-content-score-container">
|
||||
<label class="request-content-label" for="request-content-score-box">Score</label>
|
||||
<div class="request-content-box" id="request-content-score-box">
|
||||
<span class="request-content-score-text">
|
||||
{{ request.score }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
:id="'collapse' + index"
|
||||
class="accordion-collapse collapse"
|
||||
data-bs-parent="#request-invitation-container"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<div class="request-description-container">
|
||||
<button class="make-proposal-button" type="button" @click="Propose(request)">
|
||||
Propose
|
||||
</button>
|
||||
<div class="request-description-content" v-html="request.content"></div>
|
||||
<div v-for="(document_id, index) in request.document_ids" :key="document_id">
|
||||
<a :href="getDocumentDownloadLink(document_id)" download>file-{{ index }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="issuer-profile-container">
|
||||
<label class="issuer-profile-label" for="issuer-achievement-container"
|
||||
>Issuer profile</label
|
||||
>
|
||||
<div class="issuer-achievement-container" id="issuer-achievement-container">
|
||||
<div class="issuer-achievement-isssuer-container">
|
||||
<label
|
||||
class="issuer-achievement-label"
|
||||
for="issuer-achievement-isssuer-content-div"
|
||||
>Name</label
|
||||
>
|
||||
<div
|
||||
class="issuer-achievement-content-container"
|
||||
id="issuer-achievement-isssuer-content-div"
|
||||
>
|
||||
<span class="issuer-achievement-issuer-text">
|
||||
<div class="request-content-box" id="request-content-issuer-box">
|
||||
<span class="request-content-issuer-text">
|
||||
{{ request.issuer_profile.first_name }}
|
||||
{{ request.issuer_profile.last_name }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="issuer-achievement-stay-container">
|
||||
<label
|
||||
class="issuer-achievement-label"
|
||||
for="issuer-achievement-stay-content-div"
|
||||
>Stay on Freeleaps</label
|
||||
<div class="request-content-title-container">
|
||||
<label class="request-content-label" for="request-content-title-box"
|
||||
>Request</label
|
||||
>
|
||||
<div
|
||||
class="issuer-achievement-content-container"
|
||||
id="issuer-achievement-stay-content-div"
|
||||
>
|
||||
<span class="issuer-achievement-stay-content-text">
|
||||
{{ request.issuer_achievement.activeness.days_of_staying_on }} day(s)</span
|
||||
>
|
||||
<div class="request-content-box" id="request-content-title-box">
|
||||
<span class="request-content-title-text"> {{ request.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="issuer-achievement-paid-container">
|
||||
<label
|
||||
class="issuer-achievement-label"
|
||||
for="issuer-achievement-paid-content-div"
|
||||
>Total payment</label
|
||||
>
|
||||
<div
|
||||
class="issuer-achievement-content-container"
|
||||
id="issuer-achievement-stay-content-div"
|
||||
>
|
||||
<span class="issuer-achievement-paid-content-text">
|
||||
{{ request.issuer_achievement.issuer.spending.total }}
|
||||
{{ request.issuer_achievement.issuer.spending.currency }}</span
|
||||
>
|
||||
<div class="request-content-date-container">
|
||||
<label class="request-content-label" for="request-content-date-box">Date</label>
|
||||
<div class="request-content-box" id="request-content-date-box">
|
||||
<span class="request-content-date-text">
|
||||
{{ getDateFromFulltimeString(request.update_time) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="issuer-achievement-deposit-container">
|
||||
<label
|
||||
class="issuer-achievement-label"
|
||||
for="issuer-achievement-deposit-content-div"
|
||||
>Deposit</label
|
||||
<div class="request-content-score-container">
|
||||
<label class="request-content-label" for="request-content-score-box"
|
||||
>Score</label
|
||||
>
|
||||
<div
|
||||
class="issuer-achievement-content-container"
|
||||
id="issuer-achievement-deposit-content-div"
|
||||
>
|
||||
<span class="issuer-achievement-deposit-content-text">
|
||||
{{ request.issuer_achievement.issuer.deposit.available }}
|
||||
{{ request.issuer_achievement.issuer.deposit.currency }}</span
|
||||
>
|
||||
<div class="request-content-box" id="request-content-score-box">
|
||||
<span class="request-content-score-text">
|
||||
{{ request.score }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="issuer-achievement-credit-container">
|
||||
<label
|
||||
class="issuer-achievement-label"
|
||||
for="issuer-achievement-credit-content-div"
|
||||
>Credit</label
|
||||
>
|
||||
<div
|
||||
class="issuer-achievement-content-container"
|
||||
id="issuer-achievement-credit-content-div"
|
||||
>
|
||||
<span class="issuer-achievement-credit-content-text">
|
||||
{{ request.issuer_achievement.activeness.credit }}</span
|
||||
</div>
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
:id="'collapse' + index"
|
||||
class="accordion-collapse collapse"
|
||||
data-bs-parent="#request-invitation-container"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<div class="request-description-container">
|
||||
<button class="make-proposal-button" type="button" @click="Propose(request)">
|
||||
Propose
|
||||
</button>
|
||||
<div class="request-description-content" v-html="request.content"></div>
|
||||
<div v-for="(file, index) in request.attached_files" :key="index">
|
||||
<button @click="previewAttachedFile(request.id, file.document_id)">
|
||||
Preview{{ file.file_name }}
|
||||
</button>
|
||||
<button @click="downloadAttachedFile(request.id, file.document_id)">
|
||||
Download{{ file.file_name }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="issuer-profile-container">
|
||||
<!-- <label class="issuer-profile-label" for="issuer-achievement-container"
|
||||
>Issuer profile</label
|
||||
> -->
|
||||
<div class="issuer-achievement-container" id="issuer-achievement-container">
|
||||
<div class="issuer-achievement-isssuer-container">
|
||||
<label
|
||||
class="issuer-achievement-label"
|
||||
for="issuer-achievement-isssuer-content-div"
|
||||
>Name</label
|
||||
>
|
||||
<div
|
||||
class="issuer-achievement-content-container"
|
||||
id="issuer-achievement-isssuer-content-div"
|
||||
>
|
||||
<span class="issuer-achievement-issuer-text">
|
||||
{{ request.issuer_profile.first_name }}
|
||||
{{ request.issuer_profile.last_name }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="issuer-achievement-stay-container">
|
||||
<label
|
||||
class="issuer-achievement-label"
|
||||
for="issuer-achievement-stay-content-div"
|
||||
>Stay on Freeleaps</label
|
||||
>
|
||||
<div
|
||||
class="issuer-achievement-content-container"
|
||||
id="issuer-achievement-stay-content-div"
|
||||
>
|
||||
<span class="issuer-achievement-stay-content-text">
|
||||
{{ request.issuer_achievement.activeness.days_of_staying_on }}
|
||||
day(s)</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="issuer-achievement-paid-container">
|
||||
<label
|
||||
class="issuer-achievement-label"
|
||||
for="issuer-achievement-paid-content-div"
|
||||
>Total payment</label
|
||||
>
|
||||
<div
|
||||
class="issuer-achievement-content-container"
|
||||
id="issuer-achievement-stay-content-div"
|
||||
>
|
||||
<span class="issuer-achievement-paid-content-text">
|
||||
{{ request.issuer_achievement.issuer.spending.total }}
|
||||
{{ request.issuer_achievement.issuer.spending.currency }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="issuer-achievement-deposit-container">
|
||||
<label
|
||||
class="issuer-achievement-label"
|
||||
for="issuer-achievement-deposit-content-div"
|
||||
>Deposit</label
|
||||
>
|
||||
<div
|
||||
class="issuer-achievement-content-container"
|
||||
id="issuer-achievement-deposit-content-div"
|
||||
>
|
||||
<span class="issuer-achievement-deposit-content-text">
|
||||
{{ request.issuer_achievement.issuer.deposit.available }}
|
||||
{{ request.issuer_achievement.issuer.deposit.currency }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="issuer-achievement-credit-container">
|
||||
<label
|
||||
class="issuer-achievement-label"
|
||||
for="issuer-achievement-credit-content-div"
|
||||
>Credit</label
|
||||
>
|
||||
<div
|
||||
class="issuer-achievement-content-container"
|
||||
id="issuer-achievement-credit-content-div"
|
||||
>
|
||||
<span class="issuer-achievement-credit-content-text">
|
||||
{{ request.issuer_achievement.activeness.credit }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -170,7 +180,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { RequestHubApi, DateUtils, requestHubUtils } from '../../../utils/index'
|
||||
import { RequestHubApi, WorksapceApi, DateUtils, requestHubUtils } from '../../../utils/index'
|
||||
import { proposingModelEnum } from '../../../types/index'
|
||||
export default {
|
||||
name: 'RequestHub',
|
||||
@ -202,8 +212,38 @@ export default {
|
||||
getDateFromFulltimeString(fulltime) {
|
||||
return DateUtils.FromJsonToDateString(fulltime)
|
||||
},
|
||||
getDocumentDownloadLink(document_id) {
|
||||
return '/api/v1/document/download/' + document_id
|
||||
previewAttachedFile(request_id, document_id) {
|
||||
// !!! SHOULD NOT use PdfContentViewer which is designed for unlogged in users.
|
||||
// !!! Instead, should have a dedicated pdf viewer which should follow the figma design.
|
||||
// WorksapceApi.fetchAttachedFileAsMediaData(request_id, document_id)
|
||||
// .then((response) => {
|
||||
// let media_data = response.data
|
||||
// console.log(media_data)
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// this.mnx_backendErrorHandler(error)
|
||||
// })
|
||||
},
|
||||
downloadAttachedFile(request_id, document_id) {
|
||||
WorksapceApi.fetchAttachedFileAsDownload(request_id, document_id)
|
||||
.then((response) => {
|
||||
// create file link in browser's memory
|
||||
const href = URL.createObjectURL(response.data)
|
||||
|
||||
// create "a" HTML element with href to file & click
|
||||
const link = document.createElement('a')
|
||||
link.href = href
|
||||
link.setAttribute('download', 'file.pdf') //or any other extension
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
|
||||
// clean up "a" element & remove ObjectURL
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(href) //TODO: navigate to the preview page
|
||||
})
|
||||
.catch((error) => {
|
||||
this.mnx_backendErrorHandler(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -211,7 +251,6 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
.request-hub {
|
||||
@extend .container;
|
||||
@extend .my-1;
|
||||
}
|
||||
|
||||
.request-invitations-header-text {
|
||||
@ -220,8 +259,6 @@ export default {
|
||||
|
||||
.request-invitations {
|
||||
@extend .container;
|
||||
@extend .border;
|
||||
@extend .my-1;
|
||||
}
|
||||
|
||||
.request-content-container {
|
||||
@ -279,13 +316,12 @@ export default {
|
||||
}
|
||||
|
||||
.request-description-container {
|
||||
@extend .container;
|
||||
@extend .border;
|
||||
min-height: 4vh;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.make-proposal-button {
|
||||
@extend .initiate-button;
|
||||
@extend .btn;
|
||||
@extend .btn-link;
|
||||
@extend .float-end;
|
||||
}
|
||||
|
||||
@ -294,8 +330,8 @@ export default {
|
||||
}
|
||||
|
||||
.issuer-profile-container {
|
||||
@extend .container;
|
||||
@extend .border;
|
||||
border-top: 1px solid #dee2e6;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.issuer-profile-label {
|
||||
@ -321,26 +357,31 @@ export default {
|
||||
@extend .flex-colum-container-aligned-start;
|
||||
@extend .w-10;
|
||||
@extend .my-1;
|
||||
@extend .text-start;
|
||||
}
|
||||
|
||||
.issuer-achievement-issuer-text {
|
||||
@extend .text-start;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.issuer-achievement-stay-container {
|
||||
@extend .flex-colum-container-aligned-start;
|
||||
@extend .w-10;
|
||||
@extend .my-1;
|
||||
@extend .text-start;
|
||||
}
|
||||
|
||||
.issuer-achievement-stay-content-text {
|
||||
@extend .text-start;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.issuer-achievement-paid-container {
|
||||
@extend .flex-colum-container-aligned-start;
|
||||
@extend .w-10;
|
||||
@extend .my-1;
|
||||
@extend .text-start;
|
||||
}
|
||||
|
||||
.issuer-achievement-deposit-container {
|
||||
@ -351,19 +392,23 @@ export default {
|
||||
|
||||
.issuer-achievement-deposit-content-text {
|
||||
@extend .text-start;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.issuer-achievement-paid-content-text {
|
||||
@extend .text-start;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.issuer-achievement-credit-container {
|
||||
@extend .flex-colum-container-aligned-start;
|
||||
@extend .w-10;
|
||||
@extend .my-1;
|
||||
@extend .text-start;
|
||||
}
|
||||
|
||||
.issuer-achievement-credit-content-text {
|
||||
@extend .text-start;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<p class="result-text">Your proposal has been submitted!</p>
|
||||
<p class="result-text">
|
||||
You can check the status of proposals in
|
||||
<button class="goto-workspace-button" @click="gotoWorkspace">workspace</button>
|
||||
</p>
|
||||
<div class="submission-result-container">
|
||||
<div class="submission-result-card">
|
||||
<img src="@/assets/images/submited.png" alt="freeleaps" />
|
||||
<span>Your proposal has been submitted!</span>
|
||||
<span>
|
||||
You can check the status of proposals in
|
||||
<button class="btn btn-link" @click="gotoWorkspace()">My work.</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -36,12 +39,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.result-text {
|
||||
@extend .fs-2;
|
||||
}
|
||||
|
||||
.goto-workspace-button {
|
||||
@extend .proceed-button;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,111 +1,115 @@
|
||||
<template>
|
||||
<div class="making-proposal-container offcanvas-parent">
|
||||
<div
|
||||
class="offcanvas offcanvas-end offcanvas-container"
|
||||
tabindex="-1"
|
||||
id="offcanvas-copy-existing"
|
||||
aria-labelledby="offcanvas-copy-existing"
|
||||
>
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" id="offcanvas-copy-existing">Copy from existing proposals</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="offcanvas"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<div class="accordion" id="existing-proposal-item-container">
|
||||
<div
|
||||
v-for="(existingProposal, index) in existingProposals"
|
||||
:key="index"
|
||||
:id="'existing-proposal' + index"
|
||||
class="accordion-item"
|
||||
>
|
||||
<h2 class="accordion-header" :id="'existing-heading' + index">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
:data-bs-target="'#' + 'existing-proposal-collapse' + index"
|
||||
aria-expanded="false"
|
||||
:aria-controls="'collapse' + index"
|
||||
>
|
||||
{{ existingProposal.request.title }}
|
||||
</button>
|
||||
</h2>
|
||||
<div class="making-proposal-content">
|
||||
<div
|
||||
class="offcanvas offcanvas-end offcanvas-container"
|
||||
tabindex="-1"
|
||||
id="offcanvas-copy-existing"
|
||||
aria-labelledby="offcanvas-copy-existing"
|
||||
>
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" id="offcanvas-copy-existing">Copy from existing proposals</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="offcanvas"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<div class="accordion" id="existing-proposal-item-container">
|
||||
<div
|
||||
:id="'existing-proposal-collapse' + index"
|
||||
class="accordion-collapse collapse"
|
||||
:aria-labelledby="'existing-heading' + index"
|
||||
data-bs-parent="#existing-proposal-item-container"
|
||||
v-for="(existingProposal, index) in existingProposals"
|
||||
:key="index"
|
||||
:id="'existing-proposal' + index"
|
||||
class="accordion-item"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<button class="copy-existing-button" @click="copyProposal(existingProposal)">
|
||||
Copy
|
||||
<h2 class="accordion-header" :id="'existing-heading' + index">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
:data-bs-target="'#' + 'existing-proposal-collapse' + index"
|
||||
aria-expanded="false"
|
||||
:aria-controls="'collapse' + index"
|
||||
>
|
||||
{{ existingProposal.request.title }}
|
||||
</button>
|
||||
<div class="existing-request-content-text" v-html="existingProposal.content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="offcanvas offcanvas-end offcanvas-container"
|
||||
tabindex="-1"
|
||||
id="offcanvas-template"
|
||||
aria-labelledby="offcanvas-template"
|
||||
>
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" id="offcanvas-template">Apply proposal template</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="offcanvas"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<div class="accordion" id="template-item-container">
|
||||
<div
|
||||
v-for="(template, index) in templates"
|
||||
:key="index"
|
||||
:id="'template' + index"
|
||||
class="accordion-item"
|
||||
>
|
||||
<h2 class="accordion-header" :id="'heading' + index">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
:data-bs-target="'#' + 'collapse' + index"
|
||||
aria-expanded="false"
|
||||
:aria-controls="'collapse' + index"
|
||||
</h2>
|
||||
<div
|
||||
:id="'existing-proposal-collapse' + index"
|
||||
class="accordion-collapse collapse"
|
||||
:aria-labelledby="'existing-heading' + index"
|
||||
data-bs-parent="#existing-proposal-item-container"
|
||||
>
|
||||
{{ template.title }}
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
:id="'collapse' + index"
|
||||
class="accordion-collapse collapse"
|
||||
:aria-labelledby="'heading' + index"
|
||||
data-bs-parent="#template-item-container"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<button class="load-template-button" @click="applyTemplate(template)">Apply</button>
|
||||
<div class="template-content-text" v-html="template.proposal.content"></div>
|
||||
<div class="accordion-body">
|
||||
<button class="copy-existing-button" @click="copyProposal(existingProposal)">
|
||||
Copy
|
||||
</button>
|
||||
<div
|
||||
class="existing-request-content-text"
|
||||
v-html="existingProposal.content"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="request" class="request-container">
|
||||
<label class="request-label" for="request-content-container">For request:</label>
|
||||
<div class="request-content-container" id="request-content-container">
|
||||
<div class="accordion" id="accordion-request-container">
|
||||
<div
|
||||
class="offcanvas offcanvas-end offcanvas-container"
|
||||
tabindex="-1"
|
||||
id="offcanvas-template"
|
||||
aria-labelledby="offcanvas-template"
|
||||
>
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" id="offcanvas-template">Apply proposal template</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="offcanvas"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<div class="accordion" id="template-item-container">
|
||||
<div
|
||||
v-for="(template, index) in templates"
|
||||
:key="index"
|
||||
:id="'template' + index"
|
||||
class="accordion-item"
|
||||
>
|
||||
<h2 class="accordion-header" :id="'heading' + index">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
:data-bs-target="'#' + 'collapse' + index"
|
||||
aria-expanded="false"
|
||||
:aria-controls="'collapse' + index"
|
||||
>
|
||||
{{ template.title }}
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
:id="'collapse' + index"
|
||||
class="accordion-collapse collapse"
|
||||
:aria-labelledby="'heading' + index"
|
||||
data-bs-parent="#template-item-container"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<button class="load-template-button" @click="applyTemplate(template)">
|
||||
Apply
|
||||
</button>
|
||||
<div class="template-content-text" v-html="template.proposal.content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="request" class="request-container" id="request-content-container">
|
||||
<div class="accordion accordion-list" id="accordion-request-container">
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
@ -116,7 +120,7 @@
|
||||
aria-expanded="false"
|
||||
aria-controls="collapse-request-content"
|
||||
>
|
||||
{{ request.title }}
|
||||
<span class="dashed-container">{{ request.title }}</span>
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
@ -131,95 +135,158 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="proposal" class="proposal-container">
|
||||
<label class="proposal-label">Proposal:</label>
|
||||
<div class="header-bar">
|
||||
<button
|
||||
class="load-templates-button"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#offcanvas-template"
|
||||
aria-controls="offcanvas-template"
|
||||
>
|
||||
Templates...
|
||||
</button>
|
||||
<button
|
||||
class="copy-proposal-button"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#offcanvas-copy-existing"
|
||||
aria-controls="offcanvas-copy-existing"
|
||||
>
|
||||
Copy...
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="cover-letter-content-container"
|
||||
id="cover-letter-content"
|
||||
contenteditable="true"
|
||||
v-html="proposal.content"
|
||||
@blur="coverLetterDone($event)"
|
||||
></div>
|
||||
<label class="summary-content-label" for="stage-content"> Milestones</label>
|
||||
<div class="stage-content-container" id="stage-content">
|
||||
<div v-for="(stage, index) in proposal.stages" :key="index" class="stage-item-container">
|
||||
<div class="stage-item-content-container" id="stage-item-content">
|
||||
<div class="stage-payment-container">
|
||||
<label class="stage-content-label" for="stage-payment-content">Payment</label>
|
||||
<div class="stage-payment-content-container" id="stage-payment-content">
|
||||
<input
|
||||
type="text"
|
||||
class="stage-payment-content-text"
|
||||
id="stage-payment-content-text"
|
||||
v-model="stage.payment"
|
||||
/>
|
||||
<span class="stage-payment-content-span"> {{ stage.currency }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stage-duration-container">
|
||||
<label class="stage-content-label" for="stage-duration-content">Duration</label>
|
||||
<div class="stage-duration-content-container" id="stage-duration-content">
|
||||
<input
|
||||
type="text"
|
||||
class="stage-duration-content-text"
|
||||
v-model="stage.duration_in_days"
|
||||
/>
|
||||
<span class="stage-duration-content-span"> day(s)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stage-note-container">
|
||||
<label class="stage-content-label" for="stage-note-content">Notes</label>
|
||||
<input class="stage-note-content-text" id="stage-note-content" v-model="stage.note" />
|
||||
</div>
|
||||
<button class="stage-item-delete-button" @click="removeStage(index)"></button>
|
||||
<div v-if="proposal" class="proposal-container">
|
||||
<div class="proposal-header-container">
|
||||
<label class="proposal-label">Proposal:</label>
|
||||
<div class="header-bar">
|
||||
<button
|
||||
class="btn btn-link"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#offcanvas-template"
|
||||
aria-controls="offcanvas-template"
|
||||
>
|
||||
<svg-icon icon="btn-templates" />
|
||||
Templates
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-link"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#offcanvas-copy-existing"
|
||||
aria-controls="offcanvas-copy-existing"
|
||||
>
|
||||
<svg-icon icon="btn-history" />
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stage-more-action-container">
|
||||
<button class="stage-add-more-button" @click="addStage()">+ Add Milestones</button>
|
||||
<div class="cover-letter-content-container" id="cover-letter-content">
|
||||
<freeleaps-editor v-model:content="proposal.content" />
|
||||
</div>
|
||||
<div class="summary-content-container" id="summary-content">
|
||||
<span class="summary-total-stages-content-span" id="summary-total-stages-content">
|
||||
Total milestones:
|
||||
{{ summary.total_stages }}
|
||||
</span>
|
||||
<span class="summary-total-payment-content-span" id="summary-total-payment-content">
|
||||
Total payment:{{ summary.total_payment }} {{ summary.currency }}
|
||||
</span>
|
||||
<span class="summary-total-duration-content-span" id="summary-total-duration-content">
|
||||
Total duration:{{ summary.total_duration_in_days }} day(s)
|
||||
</span>
|
||||
<label class="summary-content-label" for="stage-content"> Milestones</label>
|
||||
<div class="stage-content-container" id="stage-content">
|
||||
<div v-for="(stage, index) in proposal.stages" :key="index" class="stage-item-container">
|
||||
<div class="input-container milestone-input-container">
|
||||
<div class="form-group">
|
||||
<div class="input-group-container">
|
||||
<div class="form-floating">
|
||||
<input
|
||||
class="input-email"
|
||||
:id="`stage-payment-content-${index}`"
|
||||
type="number"
|
||||
v-model="stage.payment"
|
||||
/>
|
||||
<label :for="`stage-payment-content-${index}`">Payment</label>
|
||||
</div>
|
||||
<span class="btn-start">{{ stage.currency }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-container milestone-input-container">
|
||||
<div class="form-group">
|
||||
<div class="input-group-container">
|
||||
<div class="form-floating">
|
||||
<input
|
||||
class="input-email"
|
||||
:id="`stage-duration-content-${index}`"
|
||||
type="number"
|
||||
v-model="stage.duration_in_days"
|
||||
/>
|
||||
<label :for="`stage-duration-content-${index}`">Duration</label>
|
||||
</div>
|
||||
<span class="btn-start">day(s)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-container milestone-input-container">
|
||||
<div class="form-group">
|
||||
<div class="input-group-container">
|
||||
<div class="form-floating">
|
||||
<input
|
||||
class="input-email"
|
||||
:id="`stage-note-content-${index}`"
|
||||
type="text"
|
||||
v-model="stage.note"
|
||||
/>
|
||||
<label :for="`stage-note-content-${index}`">Notes</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="stage-item-delete-button">
|
||||
<svg-icon v-if="index != 0" icon="delete" @click="removeStage(index)" />
|
||||
</button>
|
||||
<!-- <div class="stage-item-content-container" id="stage-item-content">
|
||||
<div class="stage-payment-container">
|
||||
<label class="stage-content-label" for="stage-payment-content">Payment</label>
|
||||
<div class="stage-payment-content-container" id="stage-payment-content">
|
||||
<input
|
||||
type="text"
|
||||
class="stage-payment-content-text"
|
||||
id="stage-payment-content-text"
|
||||
v-model="stage.payment"
|
||||
/>
|
||||
<span class="stage-payment-content-span"> {{ stage.currency }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stage-duration-container">
|
||||
<label class="stage-content-label" for="stage-duration-content">Duration</label>
|
||||
<div class="stage-duration-content-container" id="stage-duration-content">
|
||||
<input
|
||||
type="text"
|
||||
class="stage-duration-content-text"
|
||||
v-model="stage.duration_in_days"
|
||||
/>
|
||||
<span class="stage-duration-content-span"> day(s)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stage-note-container">
|
||||
<label class="stage-content-label" for="stage-note-content">Notes</label>
|
||||
<input class="stage-note-content-text" id="stage-note-content" v-model="stage.note" />
|
||||
</div>
|
||||
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="stage-more-action-container">
|
||||
<button class="stage-add-more-button" @click="addStage()">+ Add Milestones</button>
|
||||
</div>
|
||||
<div class="summary-content-container" id="summary-content">
|
||||
<span id="summary-total-stages-content">
|
||||
Total milestones:
|
||||
{{ summary.total_stages }}
|
||||
</span>
|
||||
<span id="summary-total-payment-content">
|
||||
Total payment:{{ summary.total_payment }} {{ summary.currency }}
|
||||
</span>
|
||||
<span id="summary-total-duration-content">
|
||||
Total duration:{{ summary.total_duration_in_days }} day(s)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="file-upload-container">
|
||||
<div class="upload-contianer">
|
||||
<label class="btn btn-link">
|
||||
<span v-if="!uploadFile">Upload file</span>
|
||||
<span v-if="uploadFile">{{ uploadFile.name }}</span>
|
||||
<input type="file" hidden @change="handleFileUpload" />
|
||||
</label>
|
||||
<svg-icon
|
||||
v-if="uploadFile"
|
||||
icon="delete"
|
||||
class-name="delete-icon"
|
||||
@click.stop="clearFile"
|
||||
/>
|
||||
</div>
|
||||
<!-- <label for="file-upload" class="file-upload-label">Upload File:</label>
|
||||
<input type="file" id="file-upload" class="file-upload-input" @change="handleFileUpload" /> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="file-upload-container">
|
||||
<label for="file-upload" class="file-upload-label">Upload File:</label>
|
||||
<input type="file" id="file-upload" class="file-upload-input" @change="handleFileUpload" />
|
||||
<div class="action-footer">
|
||||
<button class="cancel-button" @click="back">Back</button>
|
||||
<button class="submit-button" @click="submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-footer">
|
||||
<button class="cancel-button" @click="back">Back</button>
|
||||
<button class="submit-button" @click="submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -232,8 +299,11 @@ import {
|
||||
preparedProposalSummary,
|
||||
proposalUtils
|
||||
} from '../../../types/index'
|
||||
import SvgIcon from '@/components/SvgIcon.vue'
|
||||
import FreeleapsEditor from '@/components/FreeleapsEditor.vue'
|
||||
export default {
|
||||
name: 'MakeProposal',
|
||||
components: { SvgIcon, FreeleapsEditor },
|
||||
props: {
|
||||
requestId: {
|
||||
required: true,
|
||||
@ -252,7 +322,7 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
'proposal.stages': {
|
||||
handler(val, oldVal) {
|
||||
handler() {
|
||||
this.caculateSummary()
|
||||
},
|
||||
deep: true
|
||||
@ -281,6 +351,9 @@ export default {
|
||||
handleFileUpload(event) {
|
||||
this.uploadFile = event.target.files[0]
|
||||
},
|
||||
clearFile() {
|
||||
this.uploadFile = null
|
||||
},
|
||||
back() {
|
||||
this.mnx_goBack()
|
||||
},
|
||||
@ -357,7 +430,7 @@ export default {
|
||||
}
|
||||
},
|
||||
addStage() {
|
||||
this.proposal.stages.push(this.emptyStage)
|
||||
this.proposal.stages.push({ ...this.emptyStage })
|
||||
},
|
||||
|
||||
removeStage(index) {
|
||||
@ -399,6 +472,13 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
.making-proposal-container {
|
||||
@extend .container;
|
||||
padding-top: 24px;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.making-proposal-content {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0px 0px 24px 0px #d4d3e380;
|
||||
}
|
||||
|
||||
.request-label {
|
||||
@ -408,8 +488,11 @@ export default {
|
||||
|
||||
.request-container {
|
||||
@extend .container;
|
||||
@extend .my-3;
|
||||
@extend .border;
|
||||
padding: 0;
|
||||
.accordion-list {
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.request-content-container {
|
||||
@ -423,17 +506,32 @@ export default {
|
||||
|
||||
.proposal-container {
|
||||
@extend .flex-colum-container;
|
||||
@extend .border;
|
||||
}
|
||||
|
||||
.proposal-header-container {
|
||||
display: flex;
|
||||
padding: 15px 24px;
|
||||
align-items: center;
|
||||
@extend .w-100;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #e7e8eb;
|
||||
}
|
||||
|
||||
.header-bar {
|
||||
@extend .flex-row-container;
|
||||
@extend .justify-content-end;
|
||||
align-items: center;
|
||||
.btn {
|
||||
white-space: nowrap;
|
||||
padding: 0;
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.proposal-label {
|
||||
@extend .label-text;
|
||||
@extend .w-100;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #0d1637;
|
||||
}
|
||||
|
||||
.load-templates-button {
|
||||
@ -459,9 +557,8 @@ export default {
|
||||
.cover-letter-content-container {
|
||||
@extend .container;
|
||||
min-height: 40vh;
|
||||
overflow: hidden;
|
||||
@extend .text-start;
|
||||
@extend .border;
|
||||
padding: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stage-content-label {
|
||||
@ -471,13 +568,17 @@ export default {
|
||||
|
||||
.stage-content-container {
|
||||
@extend .container;
|
||||
min-height: 10vh;
|
||||
@extend .border;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.stage-item-container {
|
||||
@extend .flex-colum-container;
|
||||
@extend .m-1;
|
||||
// @extend .flex-colum-container;
|
||||
// @extend .m-1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #e7e8eb;
|
||||
}
|
||||
|
||||
.stage-item-content-label {
|
||||
@ -554,18 +655,20 @@ export default {
|
||||
}
|
||||
|
||||
.stage-more-action-container {
|
||||
@extend .flex-row-container;
|
||||
@extend .justify-content-around;
|
||||
@extend .my-1;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #e7e8eb;
|
||||
}
|
||||
|
||||
.stage-item-delete-button {
|
||||
@extend .close-button;
|
||||
width: 42px;
|
||||
@extend .btn;
|
||||
@extend .btn-link;
|
||||
}
|
||||
|
||||
.stage-add-more-button {
|
||||
@extend .proceed-button;
|
||||
@extend .w-70;
|
||||
@extend .btn;
|
||||
@extend .btn-outline-primary;
|
||||
@extend .w-100;
|
||||
}
|
||||
|
||||
.summary-container {
|
||||
@ -575,14 +678,26 @@ export default {
|
||||
}
|
||||
|
||||
.summary-content-label {
|
||||
@extend .label-text;
|
||||
@extend .proposal-label;
|
||||
@extend .w-100;
|
||||
@extend .text-start;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #e7e8eb;
|
||||
}
|
||||
|
||||
.summary-content-container {
|
||||
@extend .flex-row-container;
|
||||
@extend .justify-content-around;
|
||||
@extend .my-1;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #e7e8eb;
|
||||
|
||||
span {
|
||||
@extend .text-start;
|
||||
display: block;
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #0d1637;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-total-stages-content-span {
|
||||
@ -601,16 +716,20 @@ export default {
|
||||
}
|
||||
|
||||
.action-footer {
|
||||
@extend .flex-row-container;
|
||||
@extend .justify-content-around;
|
||||
padding: 12px 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
@extend .stop-button;
|
||||
@extend .mx-3;
|
||||
@extend .min-btn;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
@extend .proceed-button;
|
||||
@extend .mx-3;
|
||||
@extend .min-btn;
|
||||
}
|
||||
|
||||
.offcanvas-parent {
|
||||
@ -643,9 +762,13 @@ export default {
|
||||
}
|
||||
|
||||
.file-upload-container {
|
||||
@extend .flex-row-container;
|
||||
@extend .justify-content-start;
|
||||
@extend .my-3;
|
||||
padding: 12px 0;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e7e8eb;
|
||||
.btn-link {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.file-upload-label {
|
||||
@ -659,4 +782,25 @@ export default {
|
||||
@extend .form-control;
|
||||
@extend .w-30;
|
||||
}
|
||||
|
||||
.milestone-input-container {
|
||||
width: calc(33.3% - 30px);
|
||||
border: 2px dashed #9cb0f6;
|
||||
.btn-start {
|
||||
background: transparent;
|
||||
color: #0d1637;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
color: $primary;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 0.78;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="workspace-container">
|
||||
<div class="workspace-header"></div>
|
||||
<!-- <div class="workspace-header"></div> -->
|
||||
<div class="workspace-body">
|
||||
<div
|
||||
class="accordion"
|
||||
class="accordion accordion-list"
|
||||
v-for="(project, project_index) in projects"
|
||||
:key="project_index"
|
||||
:id="project.id"
|
||||
@ -18,7 +18,7 @@
|
||||
aria-expanded="false"
|
||||
:aria-controls="'collapse-' + project_index"
|
||||
>
|
||||
<div class="workspace-item-bar">
|
||||
<div class="workspace-item-bar dashed-container">
|
||||
<div class="workspace-item-bar-left">
|
||||
<div class="project-item-title-container">
|
||||
<label class="project-item-label">Summary</label>
|
||||
@ -912,7 +912,10 @@ export default {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.workspace-container {
|
||||
@extend .flex-colum-container;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.workspace-header {
|
||||
@ -921,24 +924,32 @@ export default {
|
||||
}
|
||||
|
||||
.workspace-body {
|
||||
@extend .container;
|
||||
width: 100%;
|
||||
max-width: $body-width;
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
.workspace-item-bar {
|
||||
@extend .flex-row-container;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// margin: 0 24px 0 0;
|
||||
// border: 1px dashed #aebffd;
|
||||
// border-radius: 3px;
|
||||
// flex: 1;
|
||||
}
|
||||
|
||||
.workspace-item-bar-left {
|
||||
@extend .flex-row-container;
|
||||
@extend .flex-grow-1;
|
||||
@extend .border;
|
||||
@extend .me-3;
|
||||
padding: 0;
|
||||
// @extend .border;
|
||||
// @extend .me-3;
|
||||
}
|
||||
|
||||
.workspace-item-bar-right {
|
||||
@extend .flex-row-container;
|
||||
@extend .w-20;
|
||||
@extend .border;
|
||||
// @extend .border;
|
||||
}
|
||||
|
||||
.project-item-title-container {
|
||||
@ -968,11 +979,11 @@ export default {
|
||||
.project-item-text {
|
||||
@extend .text-start;
|
||||
@extend .mx-1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.project-request-container {
|
||||
@extend .container;
|
||||
@extend .border;
|
||||
// padding: 32px;
|
||||
}
|
||||
|
||||
.float-action-container {
|
||||
@ -980,7 +991,9 @@ export default {
|
||||
}
|
||||
|
||||
.request-action-withdraw {
|
||||
@extend .initiate-button;
|
||||
@extend .btn;
|
||||
@extend .btn-link;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.project-request-content {
|
||||
@ -988,8 +1001,8 @@ export default {
|
||||
}
|
||||
|
||||
.project-request-proposals-container {
|
||||
@extend .container;
|
||||
@extend .border;
|
||||
// @extend .container;
|
||||
// @extend .border;
|
||||
}
|
||||
|
||||
.project-request-proposal-bar-container {
|
||||
@ -1071,7 +1084,8 @@ export default {
|
||||
}
|
||||
|
||||
.proposal-action-withdraw {
|
||||
@extend .initiate-button;
|
||||
@extend .btn;
|
||||
@extend .btn-link;
|
||||
}
|
||||
|
||||
.project-milestone-bar-container {
|
||||
|
||||
@ -42,9 +42,7 @@
|
||||
data-bs-parent="#existing-request-item-container"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<button class="copy-existing-button" @click="copyRequest(existingRequest)">
|
||||
Copy
|
||||
</button>
|
||||
<button class="btn btn-link" @click="copyRequest(existingRequest)">Copy</button>
|
||||
<div class="existing-request-content-text" v-html="existingRequest.content"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -84,7 +82,7 @@
|
||||
aria-expanded="false"
|
||||
:aria-controls="'collapse' + index"
|
||||
>
|
||||
{{ template.title }}
|
||||
<span class="dashed-container">{{ template.title }}</span>
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
@ -94,7 +92,10 @@
|
||||
data-bs-parent="#template-item-container"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<button class="select-template-button" @click="selectTemplate(template)">
|
||||
<button
|
||||
class="select-template-button btn btn-link"
|
||||
@click="selectTemplate(template)"
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
<div class="template-content-textarea" v-html="template.content"></div>
|
||||
@ -105,39 +106,44 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-bar">
|
||||
<div class="product-dropdown-container">
|
||||
<label>Product Name:</label>
|
||||
<select class="product-dropdown" v-model="selectedProduct">
|
||||
<option value="new">New ...</option>
|
||||
<option v-for="product in products" :value="product.name" :key="product">
|
||||
{{ product.name }}
|
||||
</option>
|
||||
</select>
|
||||
<input
|
||||
v-if="selectedProduct === 'new'"
|
||||
type="text"
|
||||
class="product-input-box"
|
||||
v-model="newProduct"
|
||||
placeholder="Enter new product name"
|
||||
<input-selector
|
||||
:select-list="products"
|
||||
:selected="selectedProduct"
|
||||
@selectedChange="selectedChange"
|
||||
/>
|
||||
<div class="upload-contianer">
|
||||
<label class="btn btn-link">
|
||||
<span v-if="!uploadFile">Upload file</span>
|
||||
<span v-if="uploadFile">{{ uploadFile.name }}</span>
|
||||
<input type="file" hidden @change="handleFileUpload" />
|
||||
</label>
|
||||
<svg-icon
|
||||
v-if="uploadFile"
|
||||
icon="delete"
|
||||
class-name="delete-icon"
|
||||
@click.stop="clearFile"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1" />
|
||||
<button
|
||||
class="action-button"
|
||||
class="action-button btn btn-link"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#offcanvas-template"
|
||||
aria-controls="offcanvas-template"
|
||||
>
|
||||
Templates...
|
||||
<svg-icon icon="btn-templates" />
|
||||
Templates
|
||||
</button>
|
||||
<button
|
||||
class="action-button"
|
||||
class="action-button btn btn-link"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#offcanvas-copy-existing"
|
||||
aria-controls="offcanvas-copy-existing"
|
||||
>
|
||||
Copy...
|
||||
<svg-icon icon="btn-history" />
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
<div class="description-container">
|
||||
@ -150,15 +156,15 @@
|
||||
@blur="descriptionDone($event)"
|
||||
contenteditable="true"
|
||||
/> -->
|
||||
<VueQuill v-model:content="content" />
|
||||
<freeleaps-editor v-model:content="content" />
|
||||
</div>
|
||||
<div class="file-upload-container">
|
||||
<!-- <div class="file-upload-container">
|
||||
<label for="file-upload" class="file-upload-label">Upload File:</label>
|
||||
<input type="file" id="file-upload" class="file-upload-input" @change="handleFileUpload" />
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="action-footer">
|
||||
<button class="cancel-button" @click="back">Back</button>
|
||||
<button class="submit-button" @click="submit">Submit</button>
|
||||
<button class="cancel-button" @click="back">Cancel</button>
|
||||
<button class="submit-button" @click="submit">(Re)Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -166,7 +172,9 @@
|
||||
<script>
|
||||
import { WorksapceApi, textAreaAujuster, requestIssueUtils } from '../../../../utils/index'
|
||||
import { requestIssuingModelEnum } from '../../../../types/index'
|
||||
import VueQuill from '@/components/VueQuill.vue'
|
||||
import FreeleapsEditor from '@/components/FreeleapsEditor.vue'
|
||||
import InputSelector from '@/components/InputSelector.vue'
|
||||
import SvgIcon from '@/components/SvgIcon.vue'
|
||||
|
||||
export default {
|
||||
name: 'IssueRequest',
|
||||
@ -180,7 +188,7 @@ export default {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
components: { VueQuill },
|
||||
components: { FreeleapsEditor, InputSelector, SvgIcon },
|
||||
mounted() {
|
||||
this.initProducts()
|
||||
this.initiateFromInput()
|
||||
@ -194,26 +202,36 @@ export default {
|
||||
request_id: null,
|
||||
availableTemplates: null,
|
||||
existingRequests: null,
|
||||
products: null,
|
||||
products: [],
|
||||
selectedProduct: null,
|
||||
newProduct: null,
|
||||
uploadFile: null
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleFileUpload(event) {
|
||||
this.uploadFile = event.target.files[0]
|
||||
if (event.target.files[0]) {
|
||||
this.uploadFile = event.target.files[0]
|
||||
}
|
||||
},
|
||||
clearFile() {
|
||||
this.uploadFile = null
|
||||
},
|
||||
initProducts() {
|
||||
WorksapceApi.fetchProducts()
|
||||
.then((response) => {
|
||||
this.products = response.data
|
||||
this.products = (response.data || []).map((p) => p.name)
|
||||
})
|
||||
.catch((error) => {
|
||||
this.mnx_backendErrorHandler(error)
|
||||
})
|
||||
},
|
||||
selectedChange(item) {
|
||||
this.selectedProduct = item.selected
|
||||
if (item.isNew) {
|
||||
this.products.push(item.selected)
|
||||
}
|
||||
},
|
||||
fetchExisting() {
|
||||
WorksapceApi.fetchMyExistingRequests('')
|
||||
.then((response) => {
|
||||
@ -290,9 +308,6 @@ export default {
|
||||
},
|
||||
submit() {
|
||||
var product = this.selectedProduct
|
||||
if (this.selectedProduct === 'new') {
|
||||
product = this.newProduct.trim()
|
||||
}
|
||||
|
||||
if (!product) {
|
||||
alert('Please input a product name or select a product')
|
||||
@ -322,19 +337,32 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.delete-icon {
|
||||
color: $primary;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 0.78;
|
||||
}
|
||||
}
|
||||
.request-issue-container {
|
||||
@extend .flex-colum-container;
|
||||
box-shadow: 0px 0px 24px 0px rgba(212, 211, 227, 0.5);
|
||||
border-top-left-radius: 12px;
|
||||
border-top-right-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
@extend .flex-row-container;
|
||||
@extend .justify-content-end;
|
||||
border-bottom: 1px solid #e7e8eb;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
@extend .initiate-button;
|
||||
@extend .mx-3;
|
||||
@extend .my-1;
|
||||
margin-left: 16px;
|
||||
// .action-btn-icon {color: $primary}
|
||||
}
|
||||
|
||||
.title-container {
|
||||
@ -350,6 +378,7 @@ export default {
|
||||
.description-container {
|
||||
@extend .w-100;
|
||||
@extend .my-3;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.input-description {
|
||||
@ -485,11 +514,13 @@ export default {
|
||||
.cancel-button {
|
||||
@extend .stop-button;
|
||||
@extend .mx-3;
|
||||
@extend .min-btn;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
@extend .proceed-button;
|
||||
@extend .mx-3;
|
||||
@extend .min-btn;
|
||||
}
|
||||
|
||||
.offcanvas-parent {
|
||||
@ -507,7 +538,7 @@ export default {
|
||||
}
|
||||
|
||||
.select-template-button {
|
||||
@extend .initiate-button;
|
||||
// @extend .initiate-button;
|
||||
@extend .float-end;
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
<template>
|
||||
<div class="submission-result-container">
|
||||
<p class="submission-result-text">The request has been submitted!</p>
|
||||
<p class="submission-result-text">
|
||||
You can find it in
|
||||
<button class="workspace-button" @click="gotoWorkspace()">workspace</button>
|
||||
</p>
|
||||
<div class="submission-result-card">
|
||||
<img src="@/assets/images/submited.png" alt="freeleaps" />
|
||||
<span>The request has been submitted!</span>
|
||||
<span>
|
||||
You can find it in
|
||||
<button class="btn btn-link" @click="gotoWorkspace()">My work.</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -36,18 +39,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.submission-result-container {
|
||||
@extend .m-3;
|
||||
@extend .p-3;
|
||||
@extend .container;
|
||||
}
|
||||
|
||||
.submission-result-text {
|
||||
@extend .fs-2;
|
||||
}
|
||||
|
||||
.workspace-button {
|
||||
@extend .inplace-proceed-button;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
export { store } from './store/index'
|
||||
export { router } from './router/index'
|
||||
export { pdfjsLib } from './pdfjs/index'
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export { pdfjsLib } from './pdfjs.js'
|
||||
@ -1,7 +0,0 @@
|
||||
import * as pdfjsLib from 'pdfjs-dist'
|
||||
//need to set worker before the pdfjs can be used.
|
||||
// pdfjsLib.GlobalWorkerOptions.workerPort = new Worker(
|
||||
// new URL('pdfjs-dist/build/pdf.worker.mjs', import.meta.url)
|
||||
// )
|
||||
|
||||
export { pdfjsLib }
|
||||
@ -58,10 +58,15 @@ import UserHistory from '../../pages/user/account/UserHistory.vue'
|
||||
import Workspace from '../../pages/user/workspace/Home.vue'
|
||||
|
||||
import FooterGuest from '../../footers/FooterGuest.vue'
|
||||
import FooterUser from '../../footers/FooterUser.vue'
|
||||
|
||||
import HeaderGuest from '../../headers/HeaderGuest.vue'
|
||||
import HeaderUser from '../../headers/HeaderUser.vue'
|
||||
|
||||
//Lab
|
||||
import LabHome from '../../pages/lab/Home.vue'
|
||||
import TranslationHome from '../../pages/lab/translation/Home.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
@ -176,7 +181,7 @@ const router = createRouter({
|
||||
name: 'my-workspace-projects',
|
||||
path: '/my-workspace-projects',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: MyWorkspaceProjects, footer: FooterGuest, header: HeaderUser },
|
||||
components: { default: MyWorkspaceProjects, footer: FooterUser, header: HeaderUser },
|
||||
props: false
|
||||
},
|
||||
|
||||
@ -184,7 +189,7 @@ const router = createRouter({
|
||||
name: 'project-manage',
|
||||
path: '/project-manage/:projectId',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: ProjectManage, footer: FooterGuest, header: HeaderUser },
|
||||
components: { default: ProjectManage, footer: FooterUser, header: HeaderUser },
|
||||
props: true,
|
||||
children: [
|
||||
{
|
||||
@ -229,14 +234,14 @@ const router = createRouter({
|
||||
name: 'my-workspace-requests',
|
||||
path: '/my-workspace-requests',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: MyWorkspaceRequests, footer: FooterGuest, header: HeaderUser },
|
||||
components: { default: MyWorkspaceRequests, footer: FooterUser, header: HeaderUser },
|
||||
props: false
|
||||
},
|
||||
{
|
||||
name: 'request-issue',
|
||||
path: '/request-issue/:loadFrom',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: IssueRequest, footer: FooterGuest, header: HeaderUser },
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL], activePath: 'Post' },
|
||||
components: { default: IssueRequest, footer: FooterUser, header: HeaderUser },
|
||||
props: (route) => {
|
||||
/**
|
||||
* This would preserve the other route.params object properties overriding only
|
||||
@ -253,7 +258,7 @@ const router = createRouter({
|
||||
name: 'request-issue2',
|
||||
path: '/request-issue/:loadFrom/:requestId',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: IssueRequest, footer: FooterGuest, header: HeaderUser },
|
||||
components: { default: IssueRequest, footer: FooterUser, header: HeaderUser },
|
||||
props: (route) => {
|
||||
/**
|
||||
* This would preserve the other route.params object properties overriding only
|
||||
@ -269,64 +274,64 @@ const router = createRouter({
|
||||
{
|
||||
name: 'request-submitted',
|
||||
path: '/request-submitted/:requestId',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: RequestSubmitted, footer: FooterGuest, header: HeaderUser },
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL], activePath: 'Post' },
|
||||
components: { default: RequestSubmitted, footer: FooterUser, header: HeaderUser },
|
||||
props: true
|
||||
},
|
||||
{
|
||||
name: 'request-deposit',
|
||||
path: '/request-deposit/:requestId',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: RequestDeposit, footer: FooterGuest, header: HeaderUser },
|
||||
components: { default: RequestDeposit, footer: FooterUser, header: HeaderUser },
|
||||
props: true
|
||||
},
|
||||
{
|
||||
name: 'request-deposited',
|
||||
path: '/request-deposited/:requestId',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: RequestDeposited, footer: FooterGuest, header: HeaderUser },
|
||||
components: { default: RequestDeposited, footer: FooterUser, header: HeaderUser },
|
||||
props: true
|
||||
},
|
||||
{
|
||||
name: 'my-workspace-proposals',
|
||||
path: '/my-workspace-proposals',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: MyWorkspaceProposals, footer: FooterGuest, header: HeaderUser },
|
||||
components: { default: MyWorkspaceProposals, footer: FooterUser, header: HeaderUser },
|
||||
props: false
|
||||
},
|
||||
//message hub
|
||||
{
|
||||
name: 'message-hub',
|
||||
path: '/message-hub',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: MessageHub, footer: FooterGuest, header: HeaderUser }
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL], activePath: 'message' },
|
||||
components: { default: MessageHub, footer: FooterUser, header: HeaderUser }
|
||||
},
|
||||
// provider hub
|
||||
{
|
||||
name: 'provider-hub',
|
||||
path: '/provider-hub',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: ProviderHub, footer: FooterGuest, header: HeaderUser }
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL], activePath: 'Providers' },
|
||||
components: { default: ProviderHub, footer: FooterUser, header: HeaderUser }
|
||||
},
|
||||
//request hub
|
||||
{
|
||||
name: 'request-hub',
|
||||
path: '/request-hub',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: RequestHub, footer: FooterGuest, header: HeaderUser }
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL], activePath: 'Requests' },
|
||||
components: { default: RequestHub, footer: FooterUser, header: HeaderUser }
|
||||
},
|
||||
{
|
||||
name: 'make-proposal',
|
||||
path: '/make-proposal/:requestId/:loadingMode',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: MakeProposal, footer: FooterGuest, header: HeaderUser },
|
||||
components: { default: MakeProposal, footer: FooterUser, header: HeaderUser },
|
||||
props: true
|
||||
},
|
||||
{
|
||||
name: 'proposal-submitted',
|
||||
path: '/proposal-submitted/:proposalId',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: ProposalSubmitted, footer: FooterGuest, header: HeaderUser },
|
||||
components: { default: ProposalSubmitted, footer: FooterUser, header: HeaderUser },
|
||||
props: true
|
||||
},
|
||||
|
||||
@ -334,7 +339,7 @@ const router = createRouter({
|
||||
name: 'request-manage',
|
||||
path: '/request-manage/:requestId',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: RequestManage, footer: FooterGuest, header: HeaderUser },
|
||||
components: { default: RequestManage, footer: FooterUser, header: HeaderUser },
|
||||
props: true
|
||||
},
|
||||
{
|
||||
@ -343,7 +348,7 @@ const router = createRouter({
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: {
|
||||
default: ReviewProposalBeforeAccetance,
|
||||
footer: FooterGuest,
|
||||
footer: FooterUser,
|
||||
header: HeaderUser
|
||||
},
|
||||
props: true
|
||||
@ -353,25 +358,37 @@ const router = createRouter({
|
||||
name: 'user-profile',
|
||||
path: '/user-profile',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: UserProfile, footer: FooterGuest, header: HeaderUser }
|
||||
components: { default: UserProfile, footer: FooterUser, header: HeaderUser }
|
||||
},
|
||||
{
|
||||
name: 'user-finance',
|
||||
path: '/user-finance',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: UserFinance, footer: FooterGuest, header: HeaderUser }
|
||||
components: { default: UserFinance, footer: FooterUser, header: HeaderUser }
|
||||
},
|
||||
{
|
||||
name: 'user-history',
|
||||
path: '/user-history',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: UserHistory, footer: FooterGuest, header: HeaderUser }
|
||||
components: { default: UserHistory, footer: FooterUser, header: HeaderUser }
|
||||
},
|
||||
{
|
||||
name: 'work-space',
|
||||
path: '/work-space',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL], active: 'workspace' },
|
||||
components: { default: Workspace, footer: FooterGuest, header: HeaderUser }
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL], activePath: 'Workspace' },
|
||||
components: { default: Workspace, footer: FooterUser, header: HeaderUser }
|
||||
},
|
||||
{
|
||||
name: 'lab-home',
|
||||
path: '/lab-home',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: LabHome, footer: FooterUser, header: HeaderUser }
|
||||
},
|
||||
{
|
||||
name: 'machine-translation',
|
||||
path: '/machine-translation',
|
||||
meta: { requiredRoles: [userRoleEnum.PERSONAL] },
|
||||
components: { default: TranslationHome, footer: FooterUser, header: HeaderUser }
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@ -35,8 +35,8 @@ class ProposalUtils {
|
||||
if (stages)
|
||||
stages.forEach((element) => {
|
||||
summary.total_stages++
|
||||
summary.total_payment += element.payment
|
||||
summary.total_duration_in_days += element.duration_in_days
|
||||
summary.total_payment += Number(element.payment)
|
||||
summary.total_duration_in_days += Number(element.duration_in_days)
|
||||
summary.currency = element.currency
|
||||
})
|
||||
}
|
||||
|
||||
@ -6,3 +6,4 @@ export { ProviderHubApi } from './providerHub'
|
||||
export { MessageHubApi } from './messageHub'
|
||||
export { HistoryApi } from './history'
|
||||
export { ContentApi } from './content'
|
||||
export { LabApi } from './lab'
|
||||
|
||||
18
frontend/src/utils/backend/lab.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { backendAxios } from './axios'
|
||||
import { userUtils } from '../store/index'
|
||||
class LabApi {
|
||||
static translate_text(input_text) {
|
||||
let jwt = userUtils.getJwtToken()
|
||||
const request = backendAxios.post(
|
||||
'/api/lab/translate_text',
|
||||
{
|
||||
input_text: input_text
|
||||
},
|
||||
{
|
||||
headers: { Authorization: `Bearer ${jwt}` }
|
||||
}
|
||||
)
|
||||
return request
|
||||
}
|
||||
}
|
||||
export { LabApi }
|
||||
@ -319,6 +319,32 @@ class WorksapceApi {
|
||||
})
|
||||
return request
|
||||
}
|
||||
static fetchAttachedFileAsMediaData(request_id, document_id) {
|
||||
let jwt = userUtils.getJwtToken()
|
||||
const request = backendAxios.post(
|
||||
'/api/workspace/request/fetch-attached-file-as-media-data',
|
||||
{
|
||||
request_id: request_id,
|
||||
document_id: document_id
|
||||
},
|
||||
{
|
||||
headers: { Authorization: `Bearer ${jwt}` }
|
||||
}
|
||||
)
|
||||
return request
|
||||
}
|
||||
static fetchAttachedFileAsDownload(request_id, document_id) {
|
||||
let jwt = userUtils.getJwtToken()
|
||||
const request = backendAxios.get('/api/workspace/request/fetch-attached-file-as-download', {
|
||||
params: {
|
||||
request_id: request_id,
|
||||
document_id: document_id
|
||||
},
|
||||
responseType: 'blob',
|
||||
headers: { Authorization: `Bearer ${jwt}` }
|
||||
})
|
||||
return request
|
||||
}
|
||||
}
|
||||
|
||||
export { WorksapceApi }
|
||||
|
||||
@ -38,6 +38,15 @@ class DateUtils {
|
||||
}
|
||||
}
|
||||
|
||||
static FromJsonToHMDateString(o) {
|
||||
let date = DateUtils.FromJson(o)
|
||||
if (date) {
|
||||
return `${date.getUTCHours().toString().padStart(2, '0')}:${date.getUTCMinutes().toString().padStart(2, '0')}`
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
static GetDeltaInDays(o) {
|
||||
let date = o instanceof Date ? o : DateUtils.FromJson(o)
|
||||
return Math.abs(Math.floor((date - Date.now()) / (1000 * 60 * 60 * 24)))
|
||||
|
||||
@ -6,13 +6,14 @@ export {
|
||||
ProviderHubApi,
|
||||
MessageHubApi,
|
||||
HistoryApi,
|
||||
ContentApi
|
||||
ContentApi,
|
||||
LabApi
|
||||
} from './backend/index'
|
||||
|
||||
export { userUtils, userProfileUtils, requestIssueUtils, requestHubUtils } from './store/index'
|
||||
|
||||
export { ValueChecker, DateUtils, OIDUtils } from './common/index'
|
||||
|
||||
export { passwordValidator } from './validator/index'
|
||||
export { passwordValidator, applicantValidator } from './validator/index'
|
||||
|
||||
export { textAreaAujuster, tooltip, elementHandler, mediaDataHandler } from './html/index'
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export { passwordValidator } from './passwordValidator'
|
||||
export { applicantValidator } from './applicantValidator'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import { defineConfig } from 'vite'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
@ -9,51 +9,71 @@ function resolve(dir) {
|
||||
}
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
createSvgIconsPlugin({
|
||||
iconDirs: [resolve('src/icons')],
|
||||
symbolId: 'icon-[name]'
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
root: resolve('src'),
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'~bootstrap': resolve('node_modules/bootstrap'),
|
||||
'~quill': resolve('node_modules/quill'),
|
||||
}
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `@import "@/assets/styles/main.scss";`
|
||||
},
|
||||
styl: {
|
||||
additionalData: ` @import '~quill/dist/quill.core.css';'~quill/dist/quill.bubble.css';'~quill/dist/quill.snow.css';`
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'^/api/': {
|
||||
target: 'http://localhost:8001/',
|
||||
ws: true,
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
},
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
supported: {
|
||||
"top-level-await": true
|
||||
},
|
||||
},
|
||||
},
|
||||
esbuild: {
|
||||
supported: {
|
||||
'top-level-await': true //browsers can handle top-level-await features
|
||||
},
|
||||
export default defineConfig(({ command, mode }) => {
|
||||
let API_URL = 'http://127.0.0.1:8001'
|
||||
let PORT = '5173'
|
||||
|
||||
if (command === 'serve') {
|
||||
// dev specific config
|
||||
console.log('loading environment variables from .env and .env.' + mode + ' under ' + process.cwd())
|
||||
const env = loadEnv(mode, process.cwd());
|
||||
API_URL = `${env.VITE_API_URL ?? 'http://127.0.0.1:8001'}`;
|
||||
PORT = `${env.VITE_PORT ?? '5173'}`;
|
||||
} else {
|
||||
// command === 'build'
|
||||
// build specific config
|
||||
}
|
||||
})
|
||||
|
||||
console.log('backend api url: ' + API_URL)
|
||||
console.log('server port: ' + PORT)
|
||||
return {
|
||||
plugins: [
|
||||
vue(),
|
||||
createSvgIconsPlugin({
|
||||
iconDirs: [resolve('src/icons')],
|
||||
symbolId: 'icon-[name]'
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
root: resolve('src'),
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'~bootstrap': resolve('node_modules/bootstrap'),
|
||||
'~quill': resolve('node_modules/quill'),
|
||||
}
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `@import "@/assets/styles/main.scss";`
|
||||
},
|
||||
styl: {
|
||||
additionalData: ` @import '~quill/dist/quill.core.css';'~quill/dist/quill.bubble.css';'~quill/dist/quill.snow.css';`
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'^/api/': {
|
||||
target: API_URL,
|
||||
ws: true,
|
||||
changeOrigin: true
|
||||
}
|
||||
},
|
||||
port: PORT,
|
||||
},
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
supported: {
|
||||
"top-level-await": true
|
||||
},
|
||||
},
|
||||
},
|
||||
esbuild: {
|
||||
supported: {
|
||||
'top-level-await': true //browsers can handle top-level-await features
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||