refactor: replace custom CRM with Monica fork
Some checks failed
Build & Push Monica Image to Gitea Registry / build-and-push (push) Failing after 9s
Some checks failed
Build & Push Monica Image to Gitea Registry / build-and-push (push) Failing after 9s
This commit is contained in:
457
resources/js/components/dashboard/DashboardLog.vue
Normal file
457
resources/js/components/dashboard/DashboardLog.vue
Normal file
@@ -0,0 +1,457 @@
|
||||
<template>
|
||||
<div class="br3 ba b--gray-monica bg-white mb3">
|
||||
<notifications group="main" position="bottom right" width="400" />
|
||||
|
||||
<div class="pa3 bb b--gray-monica tc">
|
||||
<ul>
|
||||
<li :class="[activeTab === 'calls' ? 'di pointer mr3 b' : 'di pointer mr3 black-50']" @click.prevent="setActiveTab('calls')">
|
||||
{{ $t('dashboard.tab_recent_calls') }}
|
||||
</li>
|
||||
<li :class="[activeTab === 'notes' ? 'di pointer mr3 b' : 'di pointer mr3 black-50']" @click.prevent="setActiveTab('notes')">
|
||||
{{ $t('dashboard.tab_favorite_notes') }}
|
||||
</li>
|
||||
<li :class="[activeTab === 'debts' ? 'di pointer mr3 b' : 'di pointer mr3 black-50']" @click.prevent="setActiveTab('debts')">
|
||||
{{ $t('dashboard.tab_debts') }}
|
||||
</li>
|
||||
<li :class="[activeTab === 'tasks' ? 'di pointer mr3 b' : 'di pointer mr3 black-50']" @click.prevent="setActiveTab('tasks')">
|
||||
{{ $t('dashboard.tab_tasks') }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pa4">
|
||||
<!-- Calls -->
|
||||
<div v-if="activeTab === 'calls'">
|
||||
<ul v-if="calls.length !== 0">
|
||||
<li v-for="call in calls" :key="call.id" class="pb2">
|
||||
<svg id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px"
|
||||
y="0px" viewBox="0 0 473.806 473.806" style="enable-background:new 0 0 473.806 473.806;" xml:space="preserve" width="15px"
|
||||
height="15px" class="mr2"
|
||||
>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M374.456,293.506c-9.7-10.1-21.4-15.5-33.8-15.5c-12.3,0-24.1,5.3-34.2,15.4l-31.6,31.5c-2.6-1.4-5.2-2.7-7.7-4 c-3.6-1.8-7-3.5-9.9-5.3c-29.6-18.8-56.5-43.3-82.3-75c-12.5-15.8-20.9-29.1-27-42.6c8.2-7.5,15.8-15.3,23.2-22.8 c2.8-2.8,5.6-5.7,8.4-8.5c21-21,21-48.2,0-69.2l-27.3-27.3c-3.1-3.1-6.3-6.3-9.3-9.5c-6-6.2-12.3-12.6-18.8-18.6 c-9.7-9.6-21.3-14.7-33.5-14.7s-24,5.1-34,14.7c-0.1,0.1-0.1,0.1-0.2,0.2l-34,34.3c-12.8,12.8-20.1,28.4-21.7,46.5 c-2.4,29.2,6.2,56.4,12.8,74.2c16.2,43.7,40.4,84.2,76.5,127.6c43.8,52.3,96.5,93.6,156.7,122.7c23,10.9,53.7,23.8,88,26 c2.1,0.1,4.3,0.2,6.3,0.2c23.1,0,42.5-8.3,57.7-24.8c0.1-0.2,0.3-0.3,0.4-0.5c5.2-6.3,11.2-12,17.5-18.1c4.3-4.1,8.7-8.4,13-12.9 c9.9-10.3,15.1-22.3,15.1-34.6c0-12.4-5.3-24.3-15.4-34.3L374.456,293.506z M410.256,398.806 C410.156,398.806,410.156,398.906,410.256,398.806c-3.9,4.2-7.9,8-12.2,12.2c-6.5,6.2-13.1,12.7-19.3,20 c-10.1,10.8-22,15.9-37.6,15.9c-1.5,0-3.1,0-4.6-0.1c-29.7-1.9-57.3-13.5-78-23.4c-56.6-27.4-106.3-66.3-147.6-115.6 c-34.1-41.1-56.9-79.1-72-119.9c-9.3-24.9-12.7-44.3-11.2-62.6c1-11.7,5.5-21.4,13.8-29.7l34.1-34.1c4.9-4.6,10.1-7.1,15.2-7.1 c6.3,0,11.4,3.8,14.6,7c0.1,0.1,0.2,0.2,0.3,0.3c6.1,5.7,11.9,11.6,18,17.9c3.1,3.2,6.3,6.4,9.5,9.7l27.3,27.3 c10.6,10.6,10.6,20.4,0,31c-2.9,2.9-5.7,5.8-8.6,8.6c-8.4,8.6-16.4,16.6-25.1,24.4c-0.2,0.2-0.4,0.3-0.5,0.5 c-8.6,8.6-7,17-5.2,22.7c0.1,0.3,0.2,0.6,0.3,0.9c7.1,17.2,17.1,33.4,32.3,52.7l0.1,0.1c27.6,34,56.7,60.5,88.8,80.8 c4.1,2.6,8.3,4.7,12.3,6.7c3.6,1.8,7,3.5,9.9,5.3c0.4,0.2,0.8,0.5,1.2,0.7c3.4,1.7,6.6,2.5,9.9,2.5c8.3,0,13.5-5.2,15.2-6.9 l34.2-34.2c3.4-3.4,8.8-7.5,15.1-7.5c6.2,0,11.3,3.9,14.4,7.3c0.1,0.1,0.1,0.1,0.2,0.2l55.1,55.1 C420.456,377.706,420.456,388.206,410.256,398.806z" fill="#8c8c8c" />
|
||||
<path d="M256.056,112.706c26.2,4.4,50,16.8,69,35.8s31.3,42.8,35.8,69c1.1,6.6,6.8,11.2,13.3,11.2c0.8,0,1.5-0.1,2.3-0.2 c7.4-1.2,12.3-8.2,11.1-15.6c-5.4-31.7-20.4-60.6-43.3-83.5s-51.8-37.9-83.5-43.3c-7.4-1.2-14.3,3.7-15.6,11 S248.656,111.506,256.056,112.706z" fill="#8c8c8c" />
|
||||
<path d="M473.256,209.006c-8.9-52.2-33.5-99.7-71.3-137.5s-85.3-62.4-137.5-71.3c-7.3-1.3-14.2,3.7-15.5,11 c-1.2,7.4,3.7,14.3,11.1,15.6c46.6,7.9,89.1,30,122.9,63.7c33.8,33.8,55.8,76.3,63.7,122.9c1.1,6.6,6.8,11.2,13.3,11.2 c0.8,0,1.5-0.1,2.3-0.2C469.556,223.306,474.556,216.306,473.256,209.006z" fill="#8c8c8c" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<span class="black-50 mr1 f6">
|
||||
{{ call.called_at }}
|
||||
</span>
|
||||
<span class="mr1 black-50">
|
||||
•
|
||||
</span>
|
||||
<a :href="'people/' + call.contact_id">
|
||||
{{ call.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Calls: Blank state -->
|
||||
<div v-if="calls.length === 0" class="tc mt4 mb4">
|
||||
<p>{{ $t('dashboard.tab_calls_blank') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notes -->
|
||||
<div v-else-if="activeTab === 'notes'">
|
||||
<template v-if="notes.length !== 0">
|
||||
<div v-for="note in notes" :key="note.id" class="pb3 cf">
|
||||
<div class="fl w-10">
|
||||
<avatar :contact="note.contact" :clickable="true" />
|
||||
</div>
|
||||
<div class="pl3 fl w-90">
|
||||
<svg id="Layer_1" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" width="15px" height="15px" viewBox="0 0 512 512" enable-background="new 0 0 512 512"
|
||||
xml:space="preserve"
|
||||
>
|
||||
<g>
|
||||
<path fill="none" d="M397.997,102.5H383.5v18.686c0,6.921-5.582,12.53-12.5,12.53s-12.5-5.608-12.5-12.53V102.5h-17v18.686
|
||||
c0,6.921-5.582,12.53-12.5,12.53c-6.916,0-12.5-5.608-12.5-12.53V102.5h-117v18.686c0,6.921-5.581,12.53-12.5,12.53
|
||||
c-6.919,0-12.5-5.608-12.5-12.53V102.5h-17v18.686c0,6.921-5.581,12.53-12.5,12.53s-12.5-5.608-12.5-12.53V102.5h-14.321
|
||||
c-4.613,0-10.679,1.544-10.679,6.157v325.76c0,4.612,6.065,11.083,10.679,11.083h279.818c4.613,0,6.503-6.471,6.503-11.083v-325.76
|
||||
C404.5,104.044,402.611,102.5,397.997,102.5z M383.5,386.5h-251v-16h251V386.5z M383.5,345.5h-251v-17h251V345.5z M383.5,303.5
|
||||
h-251v-17h251V303.5z M383.5,261.5h-251v-17h251V261.5z M383.5,219.5h-251v-17h251V219.5z"
|
||||
/>
|
||||
<rect x="132.5" y="244.5" width="251" height="17" />
|
||||
<rect x="132.5" y="202.5" width="251" height="17" />
|
||||
<rect x="132.5" y="286.5" width="251" height="17" />
|
||||
<rect x="132.5" y="328.5" width="251" height="17" />
|
||||
<rect x="132.5" y="370.5" width="251" height="16" />
|
||||
<path d="M397.997,85.5H383.5V62.717c0-6.919-5.582-12.529-12.5-12.529s-12.5,5.61-12.5,12.529V85.5h-17V62.717
|
||||
c0-6.919-5.582-12.529-12.5-12.529c-6.916,0-12.5,5.61-12.5,12.529V85.5h-117V62.717c0-6.919-5.581-12.529-12.5-12.529
|
||||
c-6.919,0-12.5,5.61-12.5,12.529V85.5h-17V62.717c0-6.919-5.581-12.529-12.5-12.529s-12.5,5.61-12.5,12.529V85.5h-14.321
|
||||
c-13.838,0-27.679,9.318-27.679,23.157v325.76c0,13.839,13.84,28.083,27.679,28.083h279.818c13.839,0,23.503-14.244,23.503-28.083
|
||||
v-325.76C421.5,94.818,411.836,85.5,397.997,85.5z M404.5,434.417c0,4.612-1.89,11.083-6.503,11.083H118.179
|
||||
c-4.613,0-10.679-6.471-10.679-11.083v-325.76c0-4.613,6.065-6.157,10.679-6.157H132.5v18.686c0,6.921,5.581,12.53,12.5,12.53
|
||||
s12.5-5.608,12.5-12.53V102.5h17v18.686c0,6.921,5.581,12.53,12.5,12.53c6.919,0,12.5-5.608,12.5-12.53V102.5h117v18.686
|
||||
c0,6.921,5.584,12.53,12.5,12.53c6.918,0,12.5-5.608,12.5-12.53V102.5h17v18.686c0,6.921,5.582,12.53,12.5,12.53
|
||||
s12.5-5.608,12.5-12.53V102.5h14.497c4.613,0,6.503,1.544,6.503,6.157V434.417z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<span class="black-50 mr1 f6">
|
||||
{{ note.created_at }}
|
||||
</span>
|
||||
<span class="mr1 black-50">
|
||||
•
|
||||
</span>
|
||||
<a :href="'people/' + note.contact.id">
|
||||
{{ note.name }}
|
||||
</a>
|
||||
<p v-html="compiledMarkdown(note.body)"></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Notes: Blank state -->
|
||||
<div v-if="notes.length === 0" class="tc mt4 mb4">
|
||||
<p>{{ $t('dashboard.notes_title') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Debts -->
|
||||
<div v-else-if="activeTab === 'debts'">
|
||||
<ul v-if="debts.length !== 0">
|
||||
<li v-for="debt in debts" :key="debt.id" class="pb2">
|
||||
<span class="black-50 mr1 f6">
|
||||
{{ debt.created_at | formatDate }}
|
||||
</span>
|
||||
<span class="mr1 black-50">
|
||||
•
|
||||
</span>
|
||||
<a :href="'people/' + debt.contact.hash_id">
|
||||
{{ debt.contact.first_name }}
|
||||
</a>
|
||||
<span class="mr1 black-50">
|
||||
•
|
||||
</span>
|
||||
{{ debt.amount_with_currency }}
|
||||
<span v-if="debt.in_debt === 'yes'" class="black-50 f6">
|
||||
<span class="mr1 black-50">
|
||||
•
|
||||
</span>
|
||||
{{ $t('dashboard.debts_you_owe') }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Debts: Blank state -->
|
||||
<div v-else class="tc mt4 mb4">
|
||||
<p>{{ $t('dashboard.tab_debts_blank') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tasks -->
|
||||
<div v-if="activeTab === 'tasks'">
|
||||
<ul class="tc mb3">
|
||||
<li class="di mr4">
|
||||
<span :class="[contactRelatedTasksView === true ? 'b' : 'pointer']" @click.prevent="contactRelatedTasksView = true">
|
||||
{{ $t('dashboard.tasks_tab_your_contacts') }} ({{ contactRelated(tasks).length }})
|
||||
</span>
|
||||
</li>
|
||||
<li class="di">
|
||||
<span :class="[contactRelatedTasksView === true ? 'pointer' : 'b']" @click.prevent="contactRelatedTasksView = false">
|
||||
{{ $t('dashboard.tasks_tab_your_tasks') }} ({{ customNotCompleted(tasks).length }})
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Tasks: Create task -->
|
||||
<div v-show="taskAddMode" class="br3 pa2 ba b--gray-monica mb3">
|
||||
<div class="flex items-center mb2">
|
||||
<input type="checkbox" disabled class="di mr2 pb2" />
|
||||
<input v-model="newTask.title" type="text" class="bt-0 br-0 bl-0 w-100 di bb b--gray-monica pt2 pb2" :placeholder="$t('dashboard.tasks_add_task_placeholder')" @keyup.enter="saveTask()"
|
||||
@keyup.esc="taskAddMode = false"
|
||||
/>
|
||||
<a class="pointer" href="" @click.prevent="taskAddMode = false; newTask.title=''">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="f7 relative" style="left: 20px;">
|
||||
<span v-html="$t('dashboard.tasks_add_note')"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tasks: list of contact related tasks -->
|
||||
<div v-if="contactRelated(tasks).length !== 0 && contactRelatedTasksView">
|
||||
<ul>
|
||||
<li v-for="task in contactRelated(tasks)" :key="task.id" class="pb0 mb2">
|
||||
<form-checkbox
|
||||
v-model.lazy="task.completed"
|
||||
:name="'todo'"
|
||||
:dclass="'mr1'"
|
||||
@change="updateTask(task)"
|
||||
>
|
||||
{{ task.title }}
|
||||
</form-checkbox>
|
||||
<span class="black-50 mr1 f7">
|
||||
<a :href="'people/' + task.contact.hash_id">
|
||||
{{ task.contact.first_name }}
|
||||
</a>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Tasks: list of non contact related tasks -->
|
||||
<div v-if="!contactRelatedTasksView">
|
||||
<!-- Your tasks: Blank state -->
|
||||
<div v-if="customNotCompleted(tasks).length === 0" class="tc mt4 mb4">
|
||||
<p class="mb4">
|
||||
<a v-show="!taskAddMode" class="btn pointer" href="" @click.prevent="taskAddMode = true">
|
||||
{{ $t('dashboard.task_add_cta') }}
|
||||
</a>
|
||||
</p>
|
||||
<img src="img/dashboard/blank_your_tasks.svg" :alt="$t('dashboard.tasks_tab_your_tasks')" />
|
||||
</div>
|
||||
|
||||
<!-- Add a task -->
|
||||
<p v-if="customNotCompleted(tasks).length !== 0">
|
||||
<a v-show="!taskAddMode" class="pointer" href="" @click.prevent="taskAddMode = true">
|
||||
{{ $t('dashboard.task_add_cta') }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<!-- Actual list -->
|
||||
<ul v-if="customNotCompleted(tasks).length !== 0">
|
||||
<li v-for="task in customNotCompleted(tasks)" :key="task.id" class="pb0 mb2" @mouseover="showTaskAction = task.id" @mouseleave="showTaskAction = 0; confirmDestroyTask = 0">
|
||||
<form-checkbox
|
||||
v-model.lazy="task.completed"
|
||||
:name="'todo'"
|
||||
:dclass="'mr1'"
|
||||
@change="updateTask(task)"
|
||||
>
|
||||
{{ task.title }}
|
||||
</form-checkbox>
|
||||
<a v-show="showTaskAction === task.id" class="pointer mr1" href="" @click.prevent="confirmDestroyTask = task.id">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
<ul v-show="confirmDestroyTask === task.id" class="di">
|
||||
<li class="di">
|
||||
<a class="pointer mr1" href="" @click.prevent="confirmDestroyTask = 0">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="di">
|
||||
<a class="pointer red" href="" @click.prevent="destroyTask(task)">
|
||||
{{ $t('app.delete_confirm') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul v-if="customCompleted(tasks).length !== 0 && !contactRelatedTasksView">
|
||||
<li v-for="task in customCompleted(tasks)" :key="task.id" class="pb0 mb0 f6" @mouseover="showTaskAction = task.id" @mouseleave="showTaskAction = 0; confirmDestroyTask = 0">
|
||||
<form-checkbox
|
||||
v-model.lazy="task.completed"
|
||||
:name="'todo'"
|
||||
:dclass="'mb1 mr1'"
|
||||
@change="updateTask(task)"
|
||||
>
|
||||
{{ task.title }}
|
||||
</form-checkbox>
|
||||
<a v-show="showTaskAction === task.id" class="pointer mr1" href="" @click.prevent="confirmDestroyTask = task.id">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
<ul v-show="confirmDestroyTask === task.id" class="di">
|
||||
<li class="di">
|
||||
<a class="pointer mr1" href="" @click.prevent="confirmDestroyTask = 0">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="di">
|
||||
<a class="pointer red" href="" @click.prevent="destroyTask(task)">
|
||||
{{ $t('app.delete_confirm') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Avatar from '../partials/Avatar.vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
Avatar,
|
||||
},
|
||||
|
||||
props: {
|
||||
defaultActiveTab: {
|
||||
type: String,
|
||||
default: 'calls',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
activeTab: '',
|
||||
|
||||
callsAlreadyLoaded: false,
|
||||
notesAlreadyLoaded: false,
|
||||
debtsAlreadyLoaded: false,
|
||||
tasksAlreadyLoaded: false,
|
||||
|
||||
calls: [],
|
||||
notes: [],
|
||||
debts: [],
|
||||
tasks: [],
|
||||
|
||||
taskAddMode: false,
|
||||
contactRelatedTasksView: true,
|
||||
confirmDestroyTask: 0,
|
||||
showTaskAction: 0,
|
||||
newTask: {
|
||||
id: 0,
|
||||
title: '',
|
||||
description: ''
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.setActiveTab(this.defaultActiveTab);
|
||||
},
|
||||
|
||||
setActiveTab(view) {
|
||||
this.activeTab = view;
|
||||
|
||||
this.saveTab(view);
|
||||
|
||||
switch (view) {
|
||||
case 'calls':
|
||||
if (! this.callsAlreadyLoaded) {
|
||||
this.getCalls();
|
||||
this.callsAlreadyLoaded = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'notes':
|
||||
if (! this.notesAlreadyLoaded) {
|
||||
this.getNotes();
|
||||
this.notesAlreadyLoaded = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'debts':
|
||||
if (! this.debtsAlreadyLoaded) {
|
||||
this.getDebts();
|
||||
this.debtsAlreadyLoaded = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'tasks':
|
||||
if (! this.tasksAlreadyLoaded) {
|
||||
this.getTasks();
|
||||
this.tasksAlreadyLoaded = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
saveTab(view) {
|
||||
axios.post('dashboard/setTab', {'tab':view});
|
||||
},
|
||||
|
||||
getCalls() {
|
||||
axios.get('dashboard/calls')
|
||||
.then(response => {
|
||||
this.calls = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
getNotes() {
|
||||
axios.get('dashboard/notes')
|
||||
.then(response => {
|
||||
this.notes = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
getDebts() {
|
||||
axios.get('dashboard/debts')
|
||||
.then(response => {
|
||||
this.debts = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
getTasks() {
|
||||
axios.get('tasks')
|
||||
.then(response => {
|
||||
this.tasks = response.data.data;
|
||||
});
|
||||
},
|
||||
|
||||
// All the custom tasks not yet completed
|
||||
customNotCompleted: function (tasks) {
|
||||
return tasks.filter(function (task) {
|
||||
return task.contact === null && task.completed === false;
|
||||
});
|
||||
},
|
||||
|
||||
// All the custom completed tasks
|
||||
customCompleted: function (tasks) {
|
||||
return tasks.filter(function (task) {
|
||||
return task.contact === null && task.completed === true;
|
||||
});
|
||||
},
|
||||
|
||||
// All the contact related tasks
|
||||
contactRelated: function (tasks) {
|
||||
return tasks.filter(function (task) {
|
||||
return task.contact !== null && !task.completed;
|
||||
});
|
||||
},
|
||||
|
||||
updateTask(task) {
|
||||
axios.put('tasks/' + task.id, task)
|
||||
.then(response => {
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('app.default_save_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
saveTask() {
|
||||
axios.post('tasks', this.newTask)
|
||||
.then(response => {
|
||||
this.newTask.title = '';
|
||||
this.taskAddMode = false;
|
||||
this.getTasks();
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('app.default_save_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
destroyTask(task) {
|
||||
axios.delete('tasks/' + task.id)
|
||||
.then(response => {
|
||||
this.tasks.splice(this.tasks.indexOf(task), 1);
|
||||
});
|
||||
},
|
||||
|
||||
compiledMarkdown (text) {
|
||||
return text !== undefined && text !== null ? marked(text, { sanitize: true }) : '';
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
217
resources/js/components/journal/JournalList.vue
Normal file
217
resources/js/components/journal/JournalList.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<style scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity .4s
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="mw9 center">
|
||||
<!-- Left sidebar -->
|
||||
<div :class="[dirltr ? 'fl' : 'fr']" class="w-70-ns w-100 pa2">
|
||||
<!-- Filters -->
|
||||
<div class="filter mb-4">
|
||||
<div class="d-flex pb-2">
|
||||
<div class="dt">
|
||||
<label for="start-date">{{ $t('journal.start_date') }}:</label>
|
||||
<input id="start-date" v-model="startDate" type="date" class="form-control" />
|
||||
</div>
|
||||
<div class="dt pl-2">
|
||||
<label for="end-date py-2">{{ $t('journal.end_date') }}:</label>
|
||||
<input id="end-date" v-model="endDate" type="date" class="form-control" />
|
||||
</div>
|
||||
<div class="dt pl-2">
|
||||
<label for="per-page">{{ $t('journal.per_page') }}:</label>
|
||||
<input id="per-page" v-model="perPage" type="number" class="form-control" />
|
||||
</div>
|
||||
<div class="dt pl-2">
|
||||
<label for="sort-order">{{ $t('journal.sort_order') }} :</label>
|
||||
<select id="sort-order" v-model="sortOrder" class="form-control">
|
||||
<option value="asc">
|
||||
{{ $t('journal.ascending') }}
|
||||
</option>
|
||||
<option value="desc">
|
||||
{{ $t('journal.descending') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-primary" @click="getEntries">
|
||||
{{ $t('journal.apply_filter') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- How was your day -->
|
||||
<journal-rate-day @hasRated="hasRated" />
|
||||
|
||||
<!-- Logs -->
|
||||
<div v-if="journalEntries.data" v-cy-name="'journal-entries-body'" v-cy-items="journalEntries.data.map(j => j.id)"
|
||||
:cy-object-items="journalEntries.data.map(j => j.object.id)"
|
||||
>
|
||||
<div v-for="journalEntry in journalEntries.data" :key="journalEntry.id"
|
||||
v-cy-name="'entry-body-' + journalEntry.id" class="cf"
|
||||
>
|
||||
<journal-content-rate v-if="journalEntry.journalable_type === 'App\\Models\\Journal\\Day'"
|
||||
:journal-entry="journalEntry" @deleteJournalEntry="deleteJournalEntry"
|
||||
/>
|
||||
|
||||
<journal-content-activity v-else-if="journalEntry.journalable_type === 'App\\Models\\Account\\Activity'"
|
||||
:journal-entry="journalEntry"
|
||||
/>
|
||||
|
||||
<journal-content-entry v-else-if="journalEntry.journalable_type === 'App\\Models\\Journal\\Entry'"
|
||||
:journal-entry="journalEntry" @deleteJournalEntry="deleteJournalEntry"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="(journalEntries.per_page * journalEntries.current_page) <= journalEntries.total"
|
||||
class="br3 ba b--gray-monica bg-white pr3 pb3 pt3 mb3 tc"
|
||||
>
|
||||
<p class="mb0 pointer" @click="loadMore()">
|
||||
<span v-if="!loadingMore">
|
||||
{{ $t('app.load_more') }}
|
||||
</span>
|
||||
<span v-else class="black-50">
|
||||
{{ $t('app.loading') }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="journalEntries.total === 0" v-cy-name="'journal-blank-state'"
|
||||
class="br3 ba b--gray-monica bg-white pr3 pb3 pt3 mb3 tc"
|
||||
>
|
||||
<div class="tc mb4">
|
||||
<img src="img/journal/blank.svg" :alt="$t('journal.journal_empty')" />
|
||||
</div>
|
||||
<h3>
|
||||
{{ $t('journal.journal_blank_cta') }}
|
||||
</h3>
|
||||
<p>{{ $t('journal.journal_blank_description') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right sidebar -->
|
||||
<div :class="[dirltr ? 'fl' : 'fr']" class="w-30-ns w-100 pa2">
|
||||
<a v-cy-name="'add-entry-button'" href="journal/add" class="btn btn-primary w-100 mb4">
|
||||
{{ $t('journal.journal_add') }}
|
||||
</a>
|
||||
<p>{{ $t('journal.journal_description') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
journalEntries: [],
|
||||
|
||||
day: {
|
||||
rate: ''
|
||||
},
|
||||
|
||||
showSadSmileyColor: false,
|
||||
showHappySmileyColor: false,
|
||||
loadingMore: false,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
sortBy: 'created_at', // Specify the field to sort by (e.g., 'created_at', 'updated_at')
|
||||
sortOrder: 'desc', // Specify the sort order ('asc' or 'desc')
|
||||
perPage: 30, // Specify the number of entries per page
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
},
|
||||
hasMorePage: function () {
|
||||
var total = this.journalEntries.per_page * this.journalEntries.current_page;
|
||||
|
||||
if (total >= this.journalEntries.total) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getEntries();
|
||||
},
|
||||
|
||||
getEntries() {
|
||||
axios.get('journal/entries', {
|
||||
params: {
|
||||
start_date: this.startDate,
|
||||
end_date: this.endDate,
|
||||
per_page: this.perPage,
|
||||
sort_order: this.sortOrder,
|
||||
sort_by: this.sortBy,
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
this.journalEntries = response.data;
|
||||
this.journalEntries.current_page = response.data.current_page;
|
||||
this.journalEntries.next_page_url = response.data.next_page_url;
|
||||
this.journalEntries.per_page = response.data.per_page;
|
||||
this.journalEntries.prev_page_url = response.data.prev_page_url;
|
||||
this.journalEntries.total = response.data.total;
|
||||
});
|
||||
},
|
||||
|
||||
// This event is omited from the child component
|
||||
deleteJournalEntry: function ($journalEntryId) {
|
||||
// check if the deleted entry date is today. If that's the case
|
||||
// we need to put back the Rate box. This is only necessary if
|
||||
// the user does all his actions on the same page without ever
|
||||
// reloading the page.
|
||||
this.journalEntries.data.filter(function (obj) {
|
||||
return obj.id === $journalEntryId;
|
||||
});
|
||||
|
||||
// Filter out the array without the deleted Journal Entry
|
||||
this.journalEntries.data = this.journalEntries.data.filter(function (element) {
|
||||
return element.id !== $journalEntryId;
|
||||
});
|
||||
},
|
||||
|
||||
hasRated: function (journalObject) {
|
||||
this.journalEntries.data.unshift(journalObject);
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
this.loadingMore = true;
|
||||
axios.get('journal/entries?page=' + (this.journalEntries.current_page + 1),{
|
||||
params: {
|
||||
start_date: this.startDate,
|
||||
end_date: this.endDate,
|
||||
per_page: this.perPage,
|
||||
sort_order: this.sortOrder,
|
||||
sort_by: this.sortBy,
|
||||
},
|
||||
})
|
||||
.then(response => {
|
||||
this.journalEntries.current_page = response.data.current_page;
|
||||
this.journalEntries.next_page_url = response.data.next_page_url;
|
||||
this.journalEntries.per_page = response.data.per_page;
|
||||
this.journalEntries.prev_page_url = response.data.prev_page_url;
|
||||
this.journalEntries.total = response.data.total;
|
||||
|
||||
for (var j of response.data.data) {
|
||||
this.journalEntries.data.push(j);
|
||||
}
|
||||
|
||||
this.loadingMore = false;
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
250
resources/js/components/journal/RateDay.vue
Normal file
250
resources/js/components/journal/RateDay.vue
Normal file
@@ -0,0 +1,250 @@
|
||||
<style scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity .4s
|
||||
}
|
||||
|
||||
.selected {
|
||||
border: 2px solid #0981e8;
|
||||
border-radius: 50%;
|
||||
padding: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div v-if="hasRated !== true" class="br3 ba b--gray-monica bg-white pa3 mb4">
|
||||
<transition-group name="fade" mode="out-in">
|
||||
<div v-if="hasRated === 'notYet' || hasRated === 'addComment'" :key="'rate'">
|
||||
<!-- RATE BOX -->
|
||||
<div class="flex items-center">
|
||||
<div class="fl w-70 f3 pl2">
|
||||
{{ $t('journal.journal_rate') }}
|
||||
</div>
|
||||
<div class="w-30">
|
||||
<div class="flex items-center h-100">
|
||||
<div class="flex-none w-100" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<!-- sad smiley monochrome -->
|
||||
<svg v-if="!showSadSmileyColor" v-cy-name="'sad-reaction-button'" width="42px" height="41px" viewBox="0 0 42 41"
|
||||
version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
class="pointer" :class="{ selected: day.rate === 1 }"
|
||||
@click="showComment(1)" @mouseover="showSadSmileyColor = true"
|
||||
>
|
||||
<defs />
|
||||
<g id="App" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Desktop" transform="translate(-695.000000, -165.000000)">
|
||||
<g id="Group" transform="translate(695.000000, 165.000000)">
|
||||
<path id="Fill-87" d="M41.9467391,20.5475529 C41.9467391,31.8129081 32.5673478,40.9454502 20.9978696,40.9454502 C9.42869565,40.9454502 0.0496086957,31.8129081 0.0496086957,20.5475529 C0.0496086957,9.28219757 9.42869565,0.149951854 20.9978696,0.149951854 C32.5673478,0.149951854 41.9467391,9.28219757 41.9467391,20.5475529" fill="#E5E5E5" />
|
||||
<path id="Fill-88" d="M36.4447391,28.5705697 C36.4447391,30.1432861 35.1342174,31.4187659 33.5184348,31.4187659 C31.9026522,31.4187659 30.5927391,30.1432861 30.5927391,28.5705697 C30.5927391,26.996668 33.5184348,21.6244798 33.5184348,21.6244798 C33.5184348,21.6244798 36.4447391,26.996668 36.4447391,28.5705697" fill="#399EAF" />
|
||||
<path id="Fill-89" d="M32.3226522,15.4969018 L32.6711304,15.4939383 L32.3226522,15.4939383 L32.3226522,15.4969018 L32.6711304,15.4939383 L32.3226522,15.4939383 C32.3226522,15.4942346 32.3220435,15.6601893 32.284913,15.8954892 C32.2575217,16.071816 32.2094348,16.2857789 32.1336522,16.4935185 C32.0767391,16.6496937 32.0043043,16.8014236 31.9184783,16.9350764 C31.7876087,17.1365927 31.6351304,17.2927678 31.4339565,17.4077507 C31.2312609,17.5212518 30.9661739,17.6095634 30.5346087,17.6116378 C30.282913,17.6113414 30.0866087,17.5793359 29.9283478,17.532513 C29.6532174,17.4486467 29.4806522,17.3265515 29.3254348,17.1608932 C29.2113043,17.0367236 29.1136087,16.8844009 29.035087,16.7154828 C28.9163913,16.4629947 28.8448696,16.1761303 28.8095652,15.9470537 C28.7733478,15.717977 28.7715217,15.5481699 28.7718261,15.528611 L28.7636087,15.528611 L28.7718261,15.528611 L28.7636087,15.528611 L28.7718261,15.528611 L28.076087,15.5206096 L28.7715217,15.536316 L28.7718261,15.528611 L28.076087,15.5206096 L28.7715217,15.536316 C28.784913,14.9637726 28.3192609,14.4887274 27.7312609,14.4753918 C27.1429565,14.4623525 26.655087,14.9157643 26.6416957,15.488604 L26.6413913,15.528611 C26.6413913,15.6296655 26.6468696,15.9049723 26.7065217,16.2780739 C26.7515652,16.5581223 26.8267391,16.8935877 26.9612609,17.2533536 C27.0623043,17.5227335 27.1968261,17.8063381 27.3812609,18.0863866 C27.656087,18.5051256 28.0526522,18.9188268 28.5907391,19.2172488 C29.1276087,19.5171525 29.7916957,19.6878486 30.5346087,19.6860706 C30.9835217,19.6860706 31.4026087,19.6256157 31.7836522,19.5094474 C32.4526087,19.3082274 32.9897826,18.9303843 33.368087,18.5045329 C33.6538696,18.1850703 33.8583913,17.8421962 34.0081304,17.5129541 C34.2324348,17.0186464 34.3377391,16.5521954 34.3937391,16.1906514 C34.4491304,15.8291074 34.4527826,15.5706923 34.453087,15.4939383 L34.453087,15.4788246 C34.447913,14.9059848 33.9667391,14.4454607 33.3784348,14.4504986 C32.7901304,14.4555365 32.3174783,14.924062 32.3226522,15.4969018" fill="#36301D" />
|
||||
<path id="Fill-90" d="M13.2248261,15.4969018 L13.5733043,15.4939383 L13.2248261,15.4939383 L13.2248261,15.4969018 L13.5733043,15.4939383 L13.2248261,15.4939383 C13.2248261,15.4942346 13.2242174,15.6601893 13.187087,15.8954892 C13.1596957,16.071816 13.1116087,16.2857789 13.0358261,16.4935185 C12.978913,16.6496937 12.9064783,16.8014236 12.8206522,16.9350764 C12.6897826,17.1365927 12.5373043,17.2927678 12.3361304,17.4077507 C12.1334348,17.5212518 11.8683478,17.6095634 11.4367826,17.6116378 C11.185087,17.6113414 10.9887826,17.5793359 10.8305217,17.532513 C10.5553913,17.4486467 10.3828261,17.3265515 10.2276087,17.1608932 C10.1134783,17.0367236 10.0157826,16.8844009 9.93726087,16.7154828 C9.81856522,16.4629947 9.74704348,16.1761303 9.71173913,15.9470537 C9.67552174,15.717977 9.67369565,15.5481699 9.674,15.528611 L9.66578261,15.528611 L9.674,15.528611 L9.66578261,15.528611 L9.674,15.528611 L8.97826087,15.5206096 L9.67369565,15.536316 L9.674,15.528611 L8.97826087,15.5206096 L9.67369565,15.536316 C9.68708696,14.9637726 9.22143478,14.4887274 8.63343478,14.4753918 C8.04513043,14.4623525 7.55726087,14.9157643 7.54386957,15.488604 L7.54356522,15.528611 C7.54356522,15.6296655 7.54904348,15.9049723 7.60869565,16.2780739 C7.65373913,16.5581223 7.72891304,16.8935877 7.86343478,17.2533536 C7.96447826,17.5227335 8.099,17.8063381 8.28343478,18.0863866 C8.55826087,18.5051256 8.95482609,18.9188268 9.49291304,19.2172488 C10.0297826,19.5171525 10.6938696,19.6878486 11.4367826,19.6860706 C11.8856957,19.6860706 12.3047826,19.6256157 12.6858261,19.5094474 C13.3547826,19.3082274 13.8919565,18.9303843 14.2702609,18.5045329 C14.5560435,18.1850703 14.7605652,17.8421962 14.9103043,17.5129541 C15.1346087,17.0186464 15.239913,16.5521954 15.295913,16.1906514 C15.3513043,15.8291074 15.3549565,15.5706923 15.3552609,15.4939383 L15.3552609,15.4788246 C15.350087,14.9059848 14.868913,14.4454607 14.2806087,14.4504986 C13.6923043,14.4555365 13.2196522,14.924062 13.2248261,15.4969018" fill="#36301D" />
|
||||
<path id="Fill-91" d="M14.5152609,28.1364206 L14.4954783,28.13079 L14.5152609,28.1367169 L14.5152609,28.1364206 L14.4954783,28.13079 L14.5152609,28.1367169 C14.5359565,28.0605556 14.9410435,26.8330841 15.946913,25.6951096 C16.4493913,25.1240479 17.0946087,24.5740268 17.9184783,24.1653636 C18.7438696,23.757293 19.7503478,23.4816898 21.0316522,23.4802081 C22.3558696,23.4816898 23.3818261,23.7729994 24.2145217,24.1988509 C24.8381304,24.5192025 25.353087,24.9201608 25.7794783,25.3477903 C26.418913,25.9884936 26.8541304,26.6917263 27.1222609,27.2287081 C27.2564783,27.4969026 27.349,27.7227194 27.4062174,27.8756348 C27.4348261,27.9520924 27.4546087,28.0101765 27.4661739,28.0463309 L27.4780435,28.0836707 L27.4795652,28.089005 L27.9382174,27.9615755 L27.4783478,28.0848561 L27.4795652,28.089005 L27.9382174,27.9615755 L27.4783478,28.0848561 C27.6347826,28.6369516 28.2215652,28.9614521 28.7885652,28.8091295 C29.3555652,28.6568069 29.6885217,28.0857452 29.5323913,27.5333533 C29.505913,27.4509687 29.0932174,25.9884936 27.8265217,24.4703052 C27.1925652,23.7134336 26.3367391,22.9435227 25.2082174,22.3638669 C24.0812174,21.7836184 22.6845652,21.4042936 21.0316522,21.4057753 C19.3775217,21.4042936 17.9766087,21.7868783 16.8444348,22.3709792 C15.9943913,22.8077955 15.2953043,23.3533713 14.7292174,23.9188024 C13.8797826,24.7681345 13.3249565,25.6610296 12.9749565,26.3482595 C12.6255652,27.0357858 12.4806957,27.5200177 12.467,27.5665443 C12.305087,28.117158 12.6328696,28.6911832 13.1983478,28.8488401 C13.7641304,29.0062006 14.3536522,28.6870343 14.5152609,28.1364206" fill="#36301D" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<!-- sad smiley color -->
|
||||
<svg v-if="showSadSmileyColor" v-cy-name="'sad-reaction-button'" width="42px" height="41px" viewBox="0 0 42 41"
|
||||
version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
class="pointer" :class="{ selected: day.rate === 1 }"
|
||||
@click="showComment(1)" @mouseleave="showSadSmileyColor = false"
|
||||
>
|
||||
<defs />
|
||||
<g id="App" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Desktop" transform="translate(-695.000000, -345.000000)">
|
||||
<g id="Group" transform="translate(695.000000, 345.000000)">
|
||||
<path id="Fill-87" d="M41.9467391,20.5475529 C41.9467391,31.8129081 32.5673478,40.9454502 20.9978696,40.9454502 C9.42869565,40.9454502 0.0496086957,31.8129081 0.0496086957,20.5475529 C0.0496086957,9.28219757 9.42869565,0.149951854 20.9978696,0.149951854 C32.5673478,0.149951854 41.9467391,9.28219757 41.9467391,20.5475529" fill="#FF7272" />
|
||||
<path id="Fill-88" d="M36.4447391,28.5705697 C36.4447391,30.1432861 35.1342174,31.4187659 33.5184348,31.4187659 C31.9026522,31.4187659 30.5927391,30.1432861 30.5927391,28.5705697 C30.5927391,26.996668 33.5184348,21.6244798 33.5184348,21.6244798 C33.5184348,21.6244798 36.4447391,26.996668 36.4447391,28.5705697" fill="#D0021B" />
|
||||
<path id="Fill-89" d="M32.3226522,15.4969018 L32.6711304,15.4939383 L32.3226522,15.4939383 L32.3226522,15.4969018 L32.6711304,15.4939383 L32.3226522,15.4939383 C32.3226522,15.4942346 32.3220435,15.6601893 32.284913,15.8954892 C32.2575217,16.071816 32.2094348,16.2857789 32.1336522,16.4935185 C32.0767391,16.6496937 32.0043043,16.8014236 31.9184783,16.9350764 C31.7876087,17.1365927 31.6351304,17.2927678 31.4339565,17.4077507 C31.2312609,17.5212518 30.9661739,17.6095634 30.5346087,17.6116378 C30.282913,17.6113414 30.0866087,17.5793359 29.9283478,17.532513 C29.6532174,17.4486467 29.4806522,17.3265515 29.3254348,17.1608932 C29.2113043,17.0367236 29.1136087,16.8844009 29.035087,16.7154828 C28.9163913,16.4629947 28.8448696,16.1761303 28.8095652,15.9470537 C28.7733478,15.717977 28.7715217,15.5481699 28.7718261,15.528611 L28.7636087,15.528611 L28.7718261,15.528611 L28.7636087,15.528611 L28.7718261,15.528611 L28.076087,15.5206096 L28.7715217,15.536316 L28.7718261,15.528611 L28.076087,15.5206096 L28.7715217,15.536316 C28.784913,14.9637726 28.3192609,14.4887274 27.7312609,14.4753918 C27.1429565,14.4623525 26.655087,14.9157643 26.6416957,15.488604 L26.6413913,15.528611 C26.6413913,15.6296655 26.6468696,15.9049723 26.7065217,16.2780739 C26.7515652,16.5581223 26.8267391,16.8935877 26.9612609,17.2533536 C27.0623043,17.5227335 27.1968261,17.8063381 27.3812609,18.0863866 C27.656087,18.5051256 28.0526522,18.9188268 28.5907391,19.2172488 C29.1276087,19.5171525 29.7916957,19.6878486 30.5346087,19.6860706 C30.9835217,19.6860706 31.4026087,19.6256157 31.7836522,19.5094474 C32.4526087,19.3082274 32.9897826,18.9303843 33.368087,18.5045329 C33.6538696,18.1850703 33.8583913,17.8421962 34.0081304,17.5129541 C34.2324348,17.0186464 34.3377391,16.5521954 34.3937391,16.1906514 C34.4491304,15.8291074 34.4527826,15.5706923 34.453087,15.4939383 L34.453087,15.4788246 C34.447913,14.9059848 33.9667391,14.4454607 33.3784348,14.4504986 C32.7901304,14.4555365 32.3174783,14.924062 32.3226522,15.4969018" fill="#36301D" />
|
||||
<path id="Fill-90" d="M13.2248261,15.4969018 L13.5733043,15.4939383 L13.2248261,15.4939383 L13.2248261,15.4969018 L13.5733043,15.4939383 L13.2248261,15.4939383 C13.2248261,15.4942346 13.2242174,15.6601893 13.187087,15.8954892 C13.1596957,16.071816 13.1116087,16.2857789 13.0358261,16.4935185 C12.978913,16.6496937 12.9064783,16.8014236 12.8206522,16.9350764 C12.6897826,17.1365927 12.5373043,17.2927678 12.3361304,17.4077507 C12.1334348,17.5212518 11.8683478,17.6095634 11.4367826,17.6116378 C11.185087,17.6113414 10.9887826,17.5793359 10.8305217,17.532513 C10.5553913,17.4486467 10.3828261,17.3265515 10.2276087,17.1608932 C10.1134783,17.0367236 10.0157826,16.8844009 9.93726087,16.7154828 C9.81856522,16.4629947 9.74704348,16.1761303 9.71173913,15.9470537 C9.67552174,15.717977 9.67369565,15.5481699 9.674,15.528611 L9.66578261,15.528611 L9.674,15.528611 L9.66578261,15.528611 L9.674,15.528611 L8.97826087,15.5206096 L9.67369565,15.536316 L9.674,15.528611 L8.97826087,15.5206096 L9.67369565,15.536316 C9.68708696,14.9637726 9.22143478,14.4887274 8.63343478,14.4753918 C8.04513043,14.4623525 7.55726087,14.9157643 7.54386957,15.488604 L7.54356522,15.528611 C7.54356522,15.6296655 7.54904348,15.9049723 7.60869565,16.2780739 C7.65373913,16.5581223 7.72891304,16.8935877 7.86343478,17.2533536 C7.96447826,17.5227335 8.099,17.8063381 8.28343478,18.0863866 C8.55826087,18.5051256 8.95482609,18.9188268 9.49291304,19.2172488 C10.0297826,19.5171525 10.6938696,19.6878486 11.4367826,19.6860706 C11.8856957,19.6860706 12.3047826,19.6256157 12.6858261,19.5094474 C13.3547826,19.3082274 13.8919565,18.9303843 14.2702609,18.5045329 C14.5560435,18.1850703 14.7605652,17.8421962 14.9103043,17.5129541 C15.1346087,17.0186464 15.239913,16.5521954 15.295913,16.1906514 C15.3513043,15.8291074 15.3549565,15.5706923 15.3552609,15.4939383 L15.3552609,15.4788246 C15.350087,14.9059848 14.868913,14.4454607 14.2806087,14.4504986 C13.6923043,14.4555365 13.2196522,14.924062 13.2248261,15.4969018" fill="#36301D" />
|
||||
<path id="Fill-91" d="M14.5152609,28.1364206 L14.4954783,28.13079 L14.5152609,28.1367169 L14.5152609,28.1364206 L14.4954783,28.13079 L14.5152609,28.1367169 C14.5359565,28.0605556 14.9410435,26.8330841 15.946913,25.6951096 C16.4493913,25.1240479 17.0946087,24.5740268 17.9184783,24.1653636 C18.7438696,23.757293 19.7503478,23.4816898 21.0316522,23.4802081 C22.3558696,23.4816898 23.3818261,23.7729994 24.2145217,24.1988509 C24.8381304,24.5192025 25.353087,24.9201608 25.7794783,25.3477903 C26.418913,25.9884936 26.8541304,26.6917263 27.1222609,27.2287081 C27.2564783,27.4969026 27.349,27.7227194 27.4062174,27.8756348 C27.4348261,27.9520924 27.4546087,28.0101765 27.4661739,28.0463309 L27.4780435,28.0836707 L27.4795652,28.089005 L27.9382174,27.9615755 L27.4783478,28.0848561 L27.4795652,28.089005 L27.9382174,27.9615755 L27.4783478,28.0848561 C27.6347826,28.6369516 28.2215652,28.9614521 28.7885652,28.8091295 C29.3555652,28.6568069 29.6885217,28.0857452 29.5323913,27.5333533 C29.505913,27.4509687 29.0932174,25.9884936 27.8265217,24.4703052 C27.1925652,23.7134336 26.3367391,22.9435227 25.2082174,22.3638669 C24.0812174,21.7836184 22.6845652,21.4042936 21.0316522,21.4057753 C19.3775217,21.4042936 17.9766087,21.7868783 16.8444348,22.3709792 C15.9943913,22.8077955 15.2953043,23.3533713 14.7292174,23.9188024 C13.8797826,24.7681345 13.3249565,25.6610296 12.9749565,26.3482595 C12.6255652,27.0357858 12.4806957,27.5200177 12.467,27.5665443 C12.305087,28.117158 12.6328696,28.6911832 13.1983478,28.8488401 C13.7641304,29.0062006 14.3536522,28.6870343 14.5152609,28.1364206" fill="#36301D" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<!-- mediocre day monochrome -->
|
||||
<svg v-if="!showMediocreSmileyColor" v-cy-name="'medium-reaction-button'" width="42px" height="41px" viewBox="0 0 42 41"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
class="pointer" :class="{ selected: day.rate === 2 }" @click="showComment(2)" @mouseover="showMediocreSmileyColor = true"
|
||||
>
|
||||
<defs />
|
||||
<g id="App" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Desktop" transform="translate(-754.000000, -165.000000)">
|
||||
<g id="Group-2" transform="translate(754.000000, 165.000000)">
|
||||
<path id="Fill-96" d="M41.8643309,20.5475529 C41.8643309,31.8129081 32.5521151,40.9454502 21.0661727,40.9454502 C9.57992806,40.9454502 0.268618705,31.8129081 0.268618705,20.5475529 C0.268618705,9.28219757 9.57992806,0.149951854 21.0661727,0.149951854 C32.5521151,0.149951854 41.8643309,9.28219757 41.8643309,20.5475529" fill="#E5E5E5" />
|
||||
<path id="Fill-97" d="M11.5989496,21.8043628 L11.5989496,21.8043628 C10.7680144,21.8043628 10.0881583,21.1375808 10.0881583,20.3226251 L10.0881583,16.7664546 C10.0881583,15.9514989 10.7680144,15.2847169 11.5989496,15.2847169 C12.4298849,15.2847169 13.109741,15.9514989 13.109741,16.7664546 L13.109741,20.3226251 C13.109741,21.1375808 12.4298849,21.8043628 11.5989496,21.8043628" fill="#36301D" />
|
||||
<path id="Fill-98" d="M30.534,21.8043628 L30.534,21.8043628 C29.7030647,21.8043628 29.0232086,21.1375808 29.0232086,20.3226251 L29.0232086,16.7664546 C29.0232086,15.9514989 29.7030647,15.2847169 30.534,15.2847169 C31.3649353,15.2847169 32.0447914,15.9514989 32.0447914,16.7664546 L32.0447914,20.3226251 C32.0447914,21.1375808 31.3649353,21.8043628 30.534,21.8043628" fill="#36301D" />
|
||||
<path id="Fill-99" d="M9.15690647,27.2639734 L32.9760432,27.2639734 C33.4767194,27.2639734 33.882518,26.8659787 33.882518,26.3749308 C33.882518,25.883883 33.4767194,25.4858882 32.9760432,25.4858882 L9.15690647,25.4858882 C8.65623022,25.4858882 8.25043165,25.883883 8.25043165,26.3749308 C8.25043165,26.8659787 8.65623022,27.2639734 9.15690647,27.2639734" fill="#36301D" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<!-- mediocre day color -->
|
||||
<svg v-if="showMediocreSmileyColor" v-cy-name="'medium-reaction-button'" width="42px" height="41px" viewBox="0 0 42 41"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
class="pointer" :class="{ selected: day.rate === 2 }" @click="showComment(2)" @mouseleave="showMediocreSmileyColor = false"
|
||||
>
|
||||
<defs />
|
||||
<g id="App" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Desktop" transform="translate(-754.000000, -165.000000)">
|
||||
<g id="Group-2" transform="translate(754.000000, 165.000000)">
|
||||
<path id="Fill-96" d="M41.8643309,20.5475529 C41.8643309,31.8129081 32.5521151,40.9454502 21.0661727,40.9454502 C9.57992806,40.9454502 0.268618705,31.8129081 0.268618705,20.5475529 C0.268618705,9.28219757 9.57992806,0.149951854 21.0661727,0.149951854 C32.5521151,0.149951854 41.8643309,9.28219757 41.8643309,20.5475529" fill="#FFEA7C" />
|
||||
<path id="Fill-97" d="M11.5989496,21.8043628 L11.5989496,21.8043628 C10.7680144,21.8043628 10.0881583,21.1375808 10.0881583,20.3226251 L10.0881583,16.7664546 C10.0881583,15.9514989 10.7680144,15.2847169 11.5989496,15.2847169 C12.4298849,15.2847169 13.109741,15.9514989 13.109741,16.7664546 L13.109741,20.3226251 C13.109741,21.1375808 12.4298849,21.8043628 11.5989496,21.8043628" fill="#36301D" />
|
||||
<path id="Fill-98" d="M30.534,21.8043628 L30.534,21.8043628 C29.7030647,21.8043628 29.0232086,21.1375808 29.0232086,20.3226251 L29.0232086,16.7664546 C29.0232086,15.9514989 29.7030647,15.2847169 30.534,15.2847169 C31.3649353,15.2847169 32.0447914,15.9514989 32.0447914,16.7664546 L32.0447914,20.3226251 C32.0447914,21.1375808 31.3649353,21.8043628 30.534,21.8043628" fill="#36301D" />
|
||||
<path id="Fill-99" d="M9.15690647,27.2639734 L32.9760432,27.2639734 C33.4767194,27.2639734 33.882518,26.8659787 33.882518,26.3749308 C33.882518,25.883883 33.4767194,25.4858882 32.9760432,25.4858882 L9.15690647,25.4858882 C8.65623022,25.4858882 8.25043165,25.883883 8.25043165,26.3749308 C8.25043165,26.8659787 8.65623022,27.2639734 9.15690647,27.2639734" fill="#36301D" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<!-- happy day monochrome -->
|
||||
<svg v-if="!showHappySmileyColor" v-cy-name="'happy-reaction-button'" width="42px" height="42px" viewBox="0 0 42 42"
|
||||
version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
class="pointer" :class="{ selected: day.rate === 3 }"
|
||||
@click="showComment(3)" @mouseover="showHappySmileyColor = true"
|
||||
>
|
||||
<defs />
|
||||
<g id="App" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Desktop" transform="translate(-814.000000, -165.000000)">
|
||||
<g id="Group-3" transform="translate(814.000000, 165.000000)">
|
||||
<path id="Fill-11" d="M42,20.5961538 C42,31.9710023 32.5972745,41.1923077 20.9996949,41.1923077 C9.40181023,41.1923077 0,31.9710023 0,20.5961538 C0,9.22130542 9.40181023,0 20.9996949,0 C32.5972745,0 42,9.22130542 42,20.5961538" fill="#E5E5E5" />
|
||||
<path id="Fill-12" d="M11.3076923,21 L11.3076923,21 C10.4192308,21 9.69230769,20.3391608 9.69230769,19.5314685 L9.69230769,16.006993 C9.69230769,15.1993007 10.4192308,14.5384615 11.3076923,14.5384615 C12.1961538,14.5384615 12.9230769,15.1993007 12.9230769,16.006993 L12.9230769,19.5314685 C12.9230769,20.3391608 12.1961538,21 11.3076923,21" fill="#36301D" />
|
||||
<path id="Fill-13" d="M30.6923077,21 L30.6923077,21 C29.8038462,21 29.0769231,20.3391608 29.0769231,19.5314685 L29.0769231,16.006993 C29.0769231,15.1993007 29.8038462,14.5384615 30.6923077,14.5384615 C31.5807692,14.5384615 32.3076923,15.1993007 32.3076923,16.006993 L32.3076923,19.5314685 C32.3076923,20.3391608 31.5807692,21 30.6923077,21" fill="#36301D" />
|
||||
<path id="Fill-14" d="M27.0720842,24.0517885 L27.6146482,24.3231607 L27.078106,24.0385354 L27.0720842,24.0517885 L27.6146482,24.3231607 L27.078106,24.0385354 L27.0756973,24.0432687 C27.0404698,24.1114273 26.6683226,24.8097374 25.6979098,25.4916389 C24.7250883,26.1716472 23.1440652,26.864593 20.5631224,26.868064 C19.1874426,26.8677485 18.0993036,26.6692681 17.239993,26.3852739 C15.9516293,25.9577049 15.1687955,25.3417531 14.6996974,24.8539142 C14.4660516,24.6096793 14.3127969,24.3966836 14.222771,24.2556332 C14.1776075,24.1852658 14.1484018,24.1325691 14.1330462,24.1035385 L14.1200993,24.0782946 L14.0261593,24.1278358 L14.1210026,24.0801879 L14.1200993,24.0782946 L14.0261593,24.1278358 L14.1210026,24.0801879 L13.4688418,24.4099367 L14.1264222,24.0921788 L14.1210026,24.0801879 L13.4688418,24.4099367 L14.1264222,24.0921788 C13.8828405,23.5383902 13.2565734,23.296364 12.7281606,23.5516432 C12.1997478,23.8069224 11.9688119,24.4629489 12.2123936,25.0170531 C12.2659876,25.1388551 12.7408064,26.1596563 14.0342887,27.154267 C14.6795244,27.6503102 15.5267914,28.1353091 16.6058977,28.491564 C17.6847028,28.84845 18.9935407,29.0772231 20.5631224,29.0769075 C23.7028881,29.0813252 25.8180447,28.1431978 27.1220651,27.1429072 C28.4293976,26.1448255 28.919873,25.1224465 28.9737681,25.0038001 C29.224576,24.4531669 29.0023717,23.7939849 28.4769698,23.5311325 C27.9518689,23.2682801 27.3228921,23.5014709 27.0720842,24.0517885" fill="#36301D" />
|
||||
<path id="Fill-15" d="M7.26939206,25.8461538 C5.48457391,25.8461538 4.03846154,24.5805233 4.03846154,23.0190896 C4.03846154,21.457656 5.48457391,20.1923077 7.26939206,20.1923077 C9.05324247,20.1923077 10.5,21.457656 10.5,23.0190896 C10.5,24.5805233 9.05324247,25.8461538 7.26939206,25.8461538" fill="#EB9260" />
|
||||
<path id="Fill-16" d="M37.1540074,25.8461538 C35.3691893,25.8461538 33.9230769,24.5805233 33.9230769,23.0190896 C33.9230769,21.457656 35.3691893,20.1923077 37.1540074,20.1923077 C38.9381804,20.1923077 40.3846154,21.457656 40.3846154,23.0190896 C40.3846154,24.5805233 38.9381804,25.8461538 37.1540074,25.8461538" fill="#EB9260" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<!-- happy day color -->
|
||||
<svg v-if="showHappySmileyColor" v-cy-name="'happy-reaction-button'" width="42px" height="42px" viewBox="0 0 42 42"
|
||||
version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
class="pointer" :class="{ selected: day.rate === 3 }"
|
||||
@click="showComment(3)" @mouseleave="showHappySmileyColor = false"
|
||||
>
|
||||
<defs />
|
||||
<g id="App" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Desktop" transform="translate(-814.000000, -345.000000)">
|
||||
<g id="Group-3" transform="translate(814.000000, 345.000000)">
|
||||
<path id="Fill-11" d="M42,20.5961538 C42,31.9710023 32.5972745,41.1923077 20.9996949,41.1923077 C9.40181023,41.1923077 0,31.9710023 0,20.5961538 C0,9.22130542 9.40181023,0 20.9996949,0 C32.5972745,0 42,9.22130542 42,20.5961538" fill="#C4F1AF" />
|
||||
<path id="Fill-12" d="M11.3076923,21 L11.3076923,21 C10.4192308,21 9.69230769,20.3391608 9.69230769,19.5314685 L9.69230769,16.006993 C9.69230769,15.1993007 10.4192308,14.5384615 11.3076923,14.5384615 C12.1961538,14.5384615 12.9230769,15.1993007 12.9230769,16.006993 L12.9230769,19.5314685 C12.9230769,20.3391608 12.1961538,21 11.3076923,21" fill="#36301D" />
|
||||
<path id="Fill-13" d="M30.6923077,21 L30.6923077,21 C29.8038462,21 29.0769231,20.3391608 29.0769231,19.5314685 L29.0769231,16.006993 C29.0769231,15.1993007 29.8038462,14.5384615 30.6923077,14.5384615 C31.5807692,14.5384615 32.3076923,15.1993007 32.3076923,16.006993 L32.3076923,19.5314685 C32.3076923,20.3391608 31.5807692,21 30.6923077,21" fill="#36301D" />
|
||||
<path id="Fill-14" d="M27.0720842,24.0517885 L27.6146482,24.3231607 L27.078106,24.0385354 L27.0720842,24.0517885 L27.6146482,24.3231607 L27.078106,24.0385354 L27.0756973,24.0432687 C27.0404698,24.1114273 26.6683226,24.8097374 25.6979098,25.4916389 C24.7250883,26.1716472 23.1440652,26.864593 20.5631224,26.868064 C19.1874426,26.8677485 18.0993036,26.6692681 17.239993,26.3852739 C15.9516293,25.9577049 15.1687955,25.3417531 14.6996974,24.8539142 C14.4660516,24.6096793 14.3127969,24.3966836 14.222771,24.2556332 C14.1776075,24.1852658 14.1484018,24.1325691 14.1330462,24.1035385 L14.1200993,24.0782946 L14.0261593,24.1278358 L14.1210026,24.0801879 L14.1200993,24.0782946 L14.0261593,24.1278358 L14.1210026,24.0801879 L13.4688418,24.4099367 L14.1264222,24.0921788 L14.1210026,24.0801879 L13.4688418,24.4099367 L14.1264222,24.0921788 C13.8828405,23.5383902 13.2565734,23.296364 12.7281606,23.5516432 C12.1997478,23.8069224 11.9688119,24.4629489 12.2123936,25.0170531 C12.2659876,25.1388551 12.7408064,26.1596563 14.0342887,27.154267 C14.6795244,27.6503102 15.5267914,28.1353091 16.6058977,28.491564 C17.6847028,28.84845 18.9935407,29.0772231 20.5631224,29.0769075 C23.7028881,29.0813252 25.8180447,28.1431978 27.1220651,27.1429072 C28.4293976,26.1448255 28.919873,25.1224465 28.9737681,25.0038001 C29.224576,24.4531669 29.0023717,23.7939849 28.4769698,23.5311325 C27.9518689,23.2682801 27.3228921,23.5014709 27.0720842,24.0517885" fill="#36301D" />
|
||||
<path id="Fill-15" d="M7.26939206,25.8461538 C5.48457391,25.8461538 4.03846154,24.5805233 4.03846154,23.0190896 C4.03846154,21.457656 5.48457391,20.1923077 7.26939206,20.1923077 C9.05324247,20.1923077 10.5,21.457656 10.5,23.0190896 C10.5,24.5805233 9.05324247,25.8461538 7.26939206,25.8461538" fill="#EB9260" />
|
||||
<path id="Fill-16" d="M37.1540074,25.8461538 C35.3691893,25.8461538 33.9230769,24.5805233 33.9230769,23.0190896 C33.9230769,21.457656 35.3691893,20.1923077 37.1540074,20.1923077 C38.9381804,20.1923077 40.3846154,21.457656 40.3846154,23.0190896 C40.3846154,24.5805233 38.9381804,25.8461538 37.1540074,25.8461538" fill="#EB9260" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- COMMENT BOX -->
|
||||
<div v-if="hasRated === 'addComment'" :key="'comment'" v-cy-name="'comment'" class="pt2">
|
||||
<form-textarea
|
||||
:id="'comment'"
|
||||
v-model="day.comment"
|
||||
:required="false"
|
||||
:label="$t('journal.journal_add_comment')"
|
||||
:rows="4"
|
||||
:placeholder="$t('people.life_event_create_default_description')"
|
||||
/>
|
||||
<div class="pv3">
|
||||
<div class="flex-ns justify-between">
|
||||
<div>
|
||||
<a class="btn btn-secondary tc w-auto-ns w-100 mb2 pb0-ns" href="" @click.prevent="dismiss()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<button v-cy-name="'save-entry-button'" class="btn btn-primary w-auto-ns w-100 mb2 pb0-ns" @click="rate()">
|
||||
{{ $t('app.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- COME BACK LATER BOX -->
|
||||
<div v-if="hasRated === 'justNow'" :key="'comeback'" class="flex items-center">
|
||||
<div class="w-70 f3 pl2">
|
||||
{{ $t('journal.journal_come_back') }}
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
day: {
|
||||
rate: '',
|
||||
comment: ''
|
||||
},
|
||||
|
||||
hasRated: 'notYet',
|
||||
|
||||
showSadSmileyColor: false,
|
||||
showMediocreSmileyColor: false,
|
||||
showHappySmileyColor: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.hasAlreadyRatedToday();
|
||||
},
|
||||
|
||||
hasAlreadyRatedToday() {
|
||||
axios.get('journal/hasRated')
|
||||
.then(response => {
|
||||
this.hasRated = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
showComment(rate) {
|
||||
this.day.rate = rate;
|
||||
this.hasRated = 'addComment';
|
||||
},
|
||||
|
||||
dismiss() {
|
||||
this.hasRated = 'notYet';
|
||||
this.day.rate = 0;
|
||||
this.day.comment = '';
|
||||
},
|
||||
|
||||
rate() {
|
||||
this.hasRated = 'justNow';
|
||||
|
||||
axios.post('journal/day', this.day)
|
||||
.then(response => {
|
||||
this.showSadSmileyColor = false;
|
||||
this.showMediocreSmileyColor = false;
|
||||
this.showHappySmileyColor = false;
|
||||
this.$emit('hasRated', response.data);
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
104
resources/js/components/journal/partials/JournalCalendar.vue
Normal file
104
resources/js/components/journal/partials/JournalCalendar.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div :class="[ dirltr ? 'fl' : 'fr' ]" class="journal-calendar-box relative">
|
||||
<template v-if="journalEntry.show_calendar">
|
||||
<div class="journal-calendar-text absolute tc">
|
||||
<span class="db w-100">
|
||||
{{ journalEntry.object.month_name }}
|
||||
</span>
|
||||
<span class="db w-100">
|
||||
{{ journalEntry.object.year }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="journal-calendar-svg">
|
||||
<svg width="62px" height="61px" viewBox="0 0 62 61" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<defs>
|
||||
<rect id="path-1" x="0" y="8" width="62" height="53"
|
||||
rx="4"
|
||||
/>
|
||||
<rect id="path-2" x="0" y="6" width="62" height="53"
|
||||
rx="4"
|
||||
/>
|
||||
<rect id="path-3" x="0" y="4" width="62" height="53"
|
||||
rx="4"
|
||||
/>
|
||||
<rect id="path-4" x="12" y="0" width="5" height="12"
|
||||
rx="2.5"
|
||||
/>
|
||||
<rect id="path-5" x="30" y="0" width="5" height="12"
|
||||
rx="2.5"
|
||||
/>
|
||||
<rect id="path-6" x="48" y="0" width="5" height="12"
|
||||
rx="2.5"
|
||||
/>
|
||||
</defs>
|
||||
<g id="App" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Desktop" transform="translate(-87.000000, -243.000000)">
|
||||
<g id="Group-6" transform="translate(87.000000, 243.000000)">
|
||||
<g id="Rectangle">
|
||||
<use fill="#FFFFFF" fill-rule="evenodd" xlink:href="#path-1" />
|
||||
<rect stroke="#DDE2E9" stroke-width="1" x="0.5" y="8.5" width="61"
|
||||
height="52" rx="4"
|
||||
/>
|
||||
</g>
|
||||
<g id="Rectangle">
|
||||
<use fill="#FFFFFF" fill-rule="evenodd" xlink:href="#path-2" />
|
||||
<rect stroke="#DDE2E9" stroke-width="1" x="0.5" y="6.5" width="61"
|
||||
height="52" rx="4"
|
||||
/>
|
||||
</g>
|
||||
<g id="Rectangle">
|
||||
<use fill="#FFFFFF" fill-rule="evenodd" xlink:href="#path-3" />
|
||||
<rect stroke="#DDE2E9" stroke-width="1" x="0.5" y="4.5" width="61"
|
||||
height="52" rx="4"
|
||||
/>
|
||||
</g>
|
||||
<g id="Rectangle-2">
|
||||
<use fill="#DDE2E9" fill-rule="evenodd" xlink:href="#path-4" />
|
||||
<rect stroke="#DDE2E9" stroke-width="1" x="12.5" y="0.5" width="4"
|
||||
height="11" rx="2"
|
||||
/>
|
||||
</g>
|
||||
<g id="Rectangle-2">
|
||||
<use fill="#DDE2E9" fill-rule="evenodd" xlink:href="#path-5" />
|
||||
<rect stroke="#DDE2E9" stroke-width="1" x="30.5" y="0.5" width="4"
|
||||
height="11" rx="2"
|
||||
/>
|
||||
</g>
|
||||
<g id="Rectangle-2">
|
||||
<use fill="#DDE2E9" fill-rule="evenodd" xlink:href="#path-6" />
|
||||
<rect stroke="#DDE2E9" stroke-width="1" x="48.5" y="0.5" width="4"
|
||||
height="11" rx="2"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
journalEntry: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,150 @@
|
||||
<style scoped>
|
||||
.journal-avatar-small {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.journal-initial-small {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
font-size: 13px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- Left columns: showing calendar -->
|
||||
<journal-calendar :journal-entry="journalEntry" />
|
||||
|
||||
<!-- Right column: showing logs -->
|
||||
<div :class="[ dirltr ? 'fl' : 'fr' ]" class="journal-calendar-content">
|
||||
<div class="br3 ba b--gray-monica bg-white mb3 journal-line">
|
||||
<!-- Actual log -->
|
||||
<div class="flex pb3 pt3">
|
||||
<!-- Day -->
|
||||
<div class="flex-none w-10 tc">
|
||||
<h3 class="mb0 normal fw5">
|
||||
{{ activity.day }}
|
||||
</h3>
|
||||
<p class="mb0 black-60 f6">
|
||||
{{ activity.day_name }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Log content -->
|
||||
<div class="flex-auto">
|
||||
<p class="mb1">
|
||||
<span class="pr2 f6 avenir">
|
||||
{{ $t('journal.journal_entry_type_activity') }}: {{ activity.activity_type }}
|
||||
</span>
|
||||
</p>
|
||||
<p class="mb1">
|
||||
{{ activity.summary }}
|
||||
</p>
|
||||
|
||||
<p v-if="showDescription" class="markdown" v-html="compiledMarkdown(activity.description)">
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Comment -->
|
||||
<template v-if="activity.description">
|
||||
<div class="flex-none w-5 pointer" @click="toggleDescription()">
|
||||
<div class="flex justify-center items-center h-100">
|
||||
<svg v-tooltip.top="$t('journal.journal_show_comment')" width="16px" height="13px" viewBox="0 0 16 13" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="flex-none"
|
||||
>
|
||||
<defs />
|
||||
<g id="App" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"
|
||||
stroke-linecap="square"
|
||||
>
|
||||
<g id="Desktop" transform="translate(-839.000000, -279.000000)" stroke="#979797">
|
||||
<g id="Group-4" transform="translate(839.000000, 278.000000)">
|
||||
<path id="Line-2" d="M0.5,1.5 L15.5,1.5" />
|
||||
<path id="Line-2" d="M0.5,9.5 L15.5,9.5" />
|
||||
<path id="Line-2" d="M0.5,5.5 L13.5,5.5" />
|
||||
<path id="Line-2" d="M0.5,13.5 L10.5,13.5" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Edit/Delete/Attendees -->
|
||||
<div class="flex bt b--gray-monica">
|
||||
<div class="w-10">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex-none w-30 mt2 pt1 pb2">
|
||||
<p class="mb0 f6 gray">
|
||||
{{ $t('journal.journal_created_automatically') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Avatars of the attendees -->
|
||||
<div class="flex-auto w-60 tr mt2 pa1 pr3 pb2">
|
||||
<span class="f6 gray">
|
||||
{{ $t('app.with') }}
|
||||
</span>
|
||||
<div v-for="attendees in activity.attendees" :key="attendees.id" class="dib pointer ml2" @click="redirect(attendees)">
|
||||
<img v-tooltip.bottom="attendees.complete_name" :src="attendees.information.avatar.url" class="br3 journal-avatar-small" :alt="attendees.complete_name" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
journalEntry: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showDescription: false,
|
||||
activity: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
// not necessary, just a way to add more clarity to the code
|
||||
this.activity = this.journalEntry.object;
|
||||
},
|
||||
|
||||
toggleDescription() {
|
||||
this.showDescription = !this.showDescription;
|
||||
},
|
||||
|
||||
redirect(attendee) {
|
||||
window.location.href = 'people/' + attendee.hash_id;
|
||||
},
|
||||
|
||||
compiledMarkdown (text) {
|
||||
return text !== undefined && text !== null ? marked(text, { sanitize: true }) : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
104
resources/js/components/journal/partials/JournalContentEntry.vue
Normal file
104
resources/js/components/journal/partials/JournalContentEntry.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Left columns: showing calendar -->
|
||||
<journal-calendar :journal-entry="journalEntry" />
|
||||
|
||||
<!-- Right column: showing logs -->
|
||||
<div :class="[ dirltr ? 'fl' : 'fr' ]" class="journal-calendar-content">
|
||||
<div v-tooltip.top="$t('journal.journal_created_at', { date: entry.created_at })"
|
||||
class="br3 ba b--gray-monica bg-white pr3 pb3 pt3 mb3 journal-line"
|
||||
>
|
||||
<div class="flex">
|
||||
<!-- Day -->
|
||||
<div class="flex-none w-10 tc">
|
||||
<h3 class="mb0 normal">
|
||||
{{ entry.day }}
|
||||
</h3>
|
||||
<p class="mb0">
|
||||
{{ entry.day_name }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Log content -->
|
||||
<div class="flex-auto">
|
||||
<p class="mb1">
|
||||
<span class="pr2 f6 avenir">
|
||||
{{ $t('journal.journal_entry_type_journal') }}
|
||||
</span>
|
||||
</p>
|
||||
<h3 class="mb1">
|
||||
{{ entry.title }}
|
||||
</h3>
|
||||
|
||||
<span dir="auto" class="markdown" v-html="compiledMarkdown(entry.post)"></span>
|
||||
|
||||
<ul class="f7">
|
||||
<li class="di">
|
||||
<a v-cy-name="'entry-edit-button-' + entry.id" class="pointer" :href="'journal/entries/' + entry.id + '/edit'">
|
||||
{{ $t('app.edit') }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="di">
|
||||
<confirm v-cy-name="'entry-delete-button-' + entry.id" :message="$t('journal.delete_confirmation')" @confirm="trash()">
|
||||
{{ $t('app.delete') }}
|
||||
</confirm>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Confirm from '../../partials/Confirm.vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
Confirm,
|
||||
},
|
||||
|
||||
props: {
|
||||
journalEntry: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
entry: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
// not necessary, just a way to add more clarity to the code
|
||||
this.entry = this.journalEntry.object;
|
||||
},
|
||||
|
||||
trash() {
|
||||
axios.delete('journal/' + this.entry.id)
|
||||
.then(response => {
|
||||
this.$emit('deleteJournalEntry', this.journalEntry.id);
|
||||
});
|
||||
},
|
||||
|
||||
compiledMarkdown (text) {
|
||||
return text !== undefined && text !== null ? marked(text, { sanitize: true }) : '';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
185
resources/js/components/journal/partials/JournalContentRate.vue
Normal file
185
resources/js/components/journal/partials/JournalContentRate.vue
Normal file
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Left columns: showing calendar -->
|
||||
<journal-calendar :journal-entry="journalEntry" />
|
||||
|
||||
<!-- Right column: showing logs -->
|
||||
<div :class="[ dirltr ? 'fl' : 'fr' ]" class="journal-calendar-content">
|
||||
<div class="br3 ba b--gray-monica bg-white mb3 journal-line">
|
||||
<!-- Actual log -->
|
||||
<div class="flex pb2 pt3">
|
||||
<!-- Day -->
|
||||
<div class="flex-none w-10 tc">
|
||||
<h3 class="mb0 normal fw5">
|
||||
{{ day.day }}
|
||||
</h3>
|
||||
<p class="mb0 black-60 f6">
|
||||
{{ day.day_name }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Log content -->
|
||||
<div v-if="isEditingComment" class="pt2 w-100 pr-4">
|
||||
<form-textarea
|
||||
:id="'comment'"
|
||||
v-model="day.comment"
|
||||
:required="false"
|
||||
:rows="4"
|
||||
/>
|
||||
<div class="pv3">
|
||||
<div class="flex-ns justify-between">
|
||||
<button v-cy-name="'save-entry-button'" class="btn btn-primary w-auto-ns w-100 mb2 pb0-ns" @click="saveComment()">
|
||||
{{ $t('app.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-auto flex items-center">
|
||||
<p v-if="!isEditingComment && day.comment" class="mb2">
|
||||
{{ day.comment }}
|
||||
</p>
|
||||
<p v-if="!day.comment" class="mb2">
|
||||
{{ $t('journal.journal_entry_rate') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-none">
|
||||
<!-- sad smiley color -->
|
||||
<svg v-if="day.rate === 1" width="42px" height="41px" viewBox="0 0 42 41" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="mt1" :class="[ dirltr ? 'mr3' : 'ml3' ]"
|
||||
>
|
||||
<defs />
|
||||
<g id="App" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Desktop" transform="translate(-695.000000, -345.000000)">
|
||||
<g id="Group" transform="translate(695.000000, 345.000000)">
|
||||
<path id="Fill-87" d="M41.9467391,20.5475529 C41.9467391,31.8129081 32.5673478,40.9454502 20.9978696,40.9454502 C9.42869565,40.9454502 0.0496086957,31.8129081 0.0496086957,20.5475529 C0.0496086957,9.28219757 9.42869565,0.149951854 20.9978696,0.149951854 C32.5673478,0.149951854 41.9467391,9.28219757 41.9467391,20.5475529" fill="#FF7272" />
|
||||
<path id="Fill-88" d="M36.4447391,28.5705697 C36.4447391,30.1432861 35.1342174,31.4187659 33.5184348,31.4187659 C31.9026522,31.4187659 30.5927391,30.1432861 30.5927391,28.5705697 C30.5927391,26.996668 33.5184348,21.6244798 33.5184348,21.6244798 C33.5184348,21.6244798 36.4447391,26.996668 36.4447391,28.5705697" fill="#D0021B" />
|
||||
<path id="Fill-89" d="M32.3226522,15.4969018 L32.6711304,15.4939383 L32.3226522,15.4939383 L32.3226522,15.4969018 L32.6711304,15.4939383 L32.3226522,15.4939383 C32.3226522,15.4942346 32.3220435,15.6601893 32.284913,15.8954892 C32.2575217,16.071816 32.2094348,16.2857789 32.1336522,16.4935185 C32.0767391,16.6496937 32.0043043,16.8014236 31.9184783,16.9350764 C31.7876087,17.1365927 31.6351304,17.2927678 31.4339565,17.4077507 C31.2312609,17.5212518 30.9661739,17.6095634 30.5346087,17.6116378 C30.282913,17.6113414 30.0866087,17.5793359 29.9283478,17.532513 C29.6532174,17.4486467 29.4806522,17.3265515 29.3254348,17.1608932 C29.2113043,17.0367236 29.1136087,16.8844009 29.035087,16.7154828 C28.9163913,16.4629947 28.8448696,16.1761303 28.8095652,15.9470537 C28.7733478,15.717977 28.7715217,15.5481699 28.7718261,15.528611 L28.7636087,15.528611 L28.7718261,15.528611 L28.7636087,15.528611 L28.7718261,15.528611 L28.076087,15.5206096 L28.7715217,15.536316 L28.7718261,15.528611 L28.076087,15.5206096 L28.7715217,15.536316 C28.784913,14.9637726 28.3192609,14.4887274 27.7312609,14.4753918 C27.1429565,14.4623525 26.655087,14.9157643 26.6416957,15.488604 L26.6413913,15.528611 C26.6413913,15.6296655 26.6468696,15.9049723 26.7065217,16.2780739 C26.7515652,16.5581223 26.8267391,16.8935877 26.9612609,17.2533536 C27.0623043,17.5227335 27.1968261,17.8063381 27.3812609,18.0863866 C27.656087,18.5051256 28.0526522,18.9188268 28.5907391,19.2172488 C29.1276087,19.5171525 29.7916957,19.6878486 30.5346087,19.6860706 C30.9835217,19.6860706 31.4026087,19.6256157 31.7836522,19.5094474 C32.4526087,19.3082274 32.9897826,18.9303843 33.368087,18.5045329 C33.6538696,18.1850703 33.8583913,17.8421962 34.0081304,17.5129541 C34.2324348,17.0186464 34.3377391,16.5521954 34.3937391,16.1906514 C34.4491304,15.8291074 34.4527826,15.5706923 34.453087,15.4939383 L34.453087,15.4788246 C34.447913,14.9059848 33.9667391,14.4454607 33.3784348,14.4504986 C32.7901304,14.4555365 32.3174783,14.924062 32.3226522,15.4969018" fill="#36301D" />
|
||||
<path id="Fill-90" d="M13.2248261,15.4969018 L13.5733043,15.4939383 L13.2248261,15.4939383 L13.2248261,15.4969018 L13.5733043,15.4939383 L13.2248261,15.4939383 C13.2248261,15.4942346 13.2242174,15.6601893 13.187087,15.8954892 C13.1596957,16.071816 13.1116087,16.2857789 13.0358261,16.4935185 C12.978913,16.6496937 12.9064783,16.8014236 12.8206522,16.9350764 C12.6897826,17.1365927 12.5373043,17.2927678 12.3361304,17.4077507 C12.1334348,17.5212518 11.8683478,17.6095634 11.4367826,17.6116378 C11.185087,17.6113414 10.9887826,17.5793359 10.8305217,17.532513 C10.5553913,17.4486467 10.3828261,17.3265515 10.2276087,17.1608932 C10.1134783,17.0367236 10.0157826,16.8844009 9.93726087,16.7154828 C9.81856522,16.4629947 9.74704348,16.1761303 9.71173913,15.9470537 C9.67552174,15.717977 9.67369565,15.5481699 9.674,15.528611 L9.66578261,15.528611 L9.674,15.528611 L9.66578261,15.528611 L9.674,15.528611 L8.97826087,15.5206096 L9.67369565,15.536316 L9.674,15.528611 L8.97826087,15.5206096 L9.67369565,15.536316 C9.68708696,14.9637726 9.22143478,14.4887274 8.63343478,14.4753918 C8.04513043,14.4623525 7.55726087,14.9157643 7.54386957,15.488604 L7.54356522,15.528611 C7.54356522,15.6296655 7.54904348,15.9049723 7.60869565,16.2780739 C7.65373913,16.5581223 7.72891304,16.8935877 7.86343478,17.2533536 C7.96447826,17.5227335 8.099,17.8063381 8.28343478,18.0863866 C8.55826087,18.5051256 8.95482609,18.9188268 9.49291304,19.2172488 C10.0297826,19.5171525 10.6938696,19.6878486 11.4367826,19.6860706 C11.8856957,19.6860706 12.3047826,19.6256157 12.6858261,19.5094474 C13.3547826,19.3082274 13.8919565,18.9303843 14.2702609,18.5045329 C14.5560435,18.1850703 14.7605652,17.8421962 14.9103043,17.5129541 C15.1346087,17.0186464 15.239913,16.5521954 15.295913,16.1906514 C15.3513043,15.8291074 15.3549565,15.5706923 15.3552609,15.4939383 L15.3552609,15.4788246 C15.350087,14.9059848 14.868913,14.4454607 14.2806087,14.4504986 C13.6923043,14.4555365 13.2196522,14.924062 13.2248261,15.4969018" fill="#36301D" />
|
||||
<path id="Fill-91" d="M14.5152609,28.1364206 L14.4954783,28.13079 L14.5152609,28.1367169 L14.5152609,28.1364206 L14.4954783,28.13079 L14.5152609,28.1367169 C14.5359565,28.0605556 14.9410435,26.8330841 15.946913,25.6951096 C16.4493913,25.1240479 17.0946087,24.5740268 17.9184783,24.1653636 C18.7438696,23.757293 19.7503478,23.4816898 21.0316522,23.4802081 C22.3558696,23.4816898 23.3818261,23.7729994 24.2145217,24.1988509 C24.8381304,24.5192025 25.353087,24.9201608 25.7794783,25.3477903 C26.418913,25.9884936 26.8541304,26.6917263 27.1222609,27.2287081 C27.2564783,27.4969026 27.349,27.7227194 27.4062174,27.8756348 C27.4348261,27.9520924 27.4546087,28.0101765 27.4661739,28.0463309 L27.4780435,28.0836707 L27.4795652,28.089005 L27.9382174,27.9615755 L27.4783478,28.0848561 L27.4795652,28.089005 L27.9382174,27.9615755 L27.4783478,28.0848561 C27.6347826,28.6369516 28.2215652,28.9614521 28.7885652,28.8091295 C29.3555652,28.6568069 29.6885217,28.0857452 29.5323913,27.5333533 C29.505913,27.4509687 29.0932174,25.9884936 27.8265217,24.4703052 C27.1925652,23.7134336 26.3367391,22.9435227 25.2082174,22.3638669 C24.0812174,21.7836184 22.6845652,21.4042936 21.0316522,21.4057753 C19.3775217,21.4042936 17.9766087,21.7868783 16.8444348,22.3709792 C15.9943913,22.8077955 15.2953043,23.3533713 14.7292174,23.9188024 C13.8797826,24.7681345 13.3249565,25.6610296 12.9749565,26.3482595 C12.6255652,27.0357858 12.4806957,27.5200177 12.467,27.5665443 C12.305087,28.117158 12.6328696,28.6911832 13.1983478,28.8488401 C13.7641304,29.0062006 14.3536522,28.6870343 14.5152609,28.1364206" fill="#36301D" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<!-- mediocre day color -->
|
||||
<svg v-else-if="day.rate === 2" width="42px" height="41px" viewBox="0 0 42 41" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="mt1" :class="[ dirltr ? 'mr3' : 'ml3' ]"
|
||||
>
|
||||
<defs />
|
||||
<g id="App" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Desktop" transform="translate(-754.000000, -165.000000)">
|
||||
<g id="Group-2" transform="translate(754.000000, 165.000000)">
|
||||
<path id="Fill-96" d="M41.8643309,20.5475529 C41.8643309,31.8129081 32.5521151,40.9454502 21.0661727,40.9454502 C9.57992806,40.9454502 0.268618705,31.8129081 0.268618705,20.5475529 C0.268618705,9.28219757 9.57992806,0.149951854 21.0661727,0.149951854 C32.5521151,0.149951854 41.8643309,9.28219757 41.8643309,20.5475529" fill="#FFEA7C" />
|
||||
<path id="Fill-97" d="M11.5989496,21.8043628 L11.5989496,21.8043628 C10.7680144,21.8043628 10.0881583,21.1375808 10.0881583,20.3226251 L10.0881583,16.7664546 C10.0881583,15.9514989 10.7680144,15.2847169 11.5989496,15.2847169 C12.4298849,15.2847169 13.109741,15.9514989 13.109741,16.7664546 L13.109741,20.3226251 C13.109741,21.1375808 12.4298849,21.8043628 11.5989496,21.8043628" fill="#36301D" />
|
||||
<path id="Fill-98" d="M30.534,21.8043628 L30.534,21.8043628 C29.7030647,21.8043628 29.0232086,21.1375808 29.0232086,20.3226251 L29.0232086,16.7664546 C29.0232086,15.9514989 29.7030647,15.2847169 30.534,15.2847169 C31.3649353,15.2847169 32.0447914,15.9514989 32.0447914,16.7664546 L32.0447914,20.3226251 C32.0447914,21.1375808 31.3649353,21.8043628 30.534,21.8043628" fill="#36301D" />
|
||||
<path id="Fill-99" d="M9.15690647,27.2639734 L32.9760432,27.2639734 C33.4767194,27.2639734 33.882518,26.8659787 33.882518,26.3749308 C33.882518,25.883883 33.4767194,25.4858882 32.9760432,25.4858882 L9.15690647,25.4858882 C8.65623022,25.4858882 8.25043165,25.883883 8.25043165,26.3749308 C8.25043165,26.8659787 8.65623022,27.2639734 9.15690647,27.2639734" fill="#36301D" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<!-- happy day color -->
|
||||
<svg v-else-if="day.rate === 3" width="42px" height="42px" viewBox="0 0 42 42" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="mt1" :class="[ dirltr ? 'mr3' : 'ml3' ]"
|
||||
>
|
||||
<defs />
|
||||
<g id="App" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Desktop" transform="translate(-814.000000, -345.000000)">
|
||||
<g id="Group-3" transform="translate(814.000000, 345.000000)">
|
||||
<path id="Fill-11" d="M42,20.5961538 C42,31.9710023 32.5972745,41.1923077 20.9996949,41.1923077 C9.40181023,41.1923077 0,31.9710023 0,20.5961538 C0,9.22130542 9.40181023,0 20.9996949,0 C32.5972745,0 42,9.22130542 42,20.5961538" fill="#C4F1AF" />
|
||||
<path id="Fill-12" d="M11.3076923,21 L11.3076923,21 C10.4192308,21 9.69230769,20.3391608 9.69230769,19.5314685 L9.69230769,16.006993 C9.69230769,15.1993007 10.4192308,14.5384615 11.3076923,14.5384615 C12.1961538,14.5384615 12.9230769,15.1993007 12.9230769,16.006993 L12.9230769,19.5314685 C12.9230769,20.3391608 12.1961538,21 11.3076923,21" fill="#36301D" />
|
||||
<path id="Fill-13" d="M30.6923077,21 L30.6923077,21 C29.8038462,21 29.0769231,20.3391608 29.0769231,19.5314685 L29.0769231,16.006993 C29.0769231,15.1993007 29.8038462,14.5384615 30.6923077,14.5384615 C31.5807692,14.5384615 32.3076923,15.1993007 32.3076923,16.006993 L32.3076923,19.5314685 C32.3076923,20.3391608 31.5807692,21 30.6923077,21" fill="#36301D" />
|
||||
<path id="Fill-14" d="M27.0720842,24.0517885 L27.6146482,24.3231607 L27.078106,24.0385354 L27.0720842,24.0517885 L27.6146482,24.3231607 L27.078106,24.0385354 L27.0756973,24.0432687 C27.0404698,24.1114273 26.6683226,24.8097374 25.6979098,25.4916389 C24.7250883,26.1716472 23.1440652,26.864593 20.5631224,26.868064 C19.1874426,26.8677485 18.0993036,26.6692681 17.239993,26.3852739 C15.9516293,25.9577049 15.1687955,25.3417531 14.6996974,24.8539142 C14.4660516,24.6096793 14.3127969,24.3966836 14.222771,24.2556332 C14.1776075,24.1852658 14.1484018,24.1325691 14.1330462,24.1035385 L14.1200993,24.0782946 L14.0261593,24.1278358 L14.1210026,24.0801879 L14.1200993,24.0782946 L14.0261593,24.1278358 L14.1210026,24.0801879 L13.4688418,24.4099367 L14.1264222,24.0921788 L14.1210026,24.0801879 L13.4688418,24.4099367 L14.1264222,24.0921788 C13.8828405,23.5383902 13.2565734,23.296364 12.7281606,23.5516432 C12.1997478,23.8069224 11.9688119,24.4629489 12.2123936,25.0170531 C12.2659876,25.1388551 12.7408064,26.1596563 14.0342887,27.154267 C14.6795244,27.6503102 15.5267914,28.1353091 16.6058977,28.491564 C17.6847028,28.84845 18.9935407,29.0772231 20.5631224,29.0769075 C23.7028881,29.0813252 25.8180447,28.1431978 27.1220651,27.1429072 C28.4293976,26.1448255 28.919873,25.1224465 28.9737681,25.0038001 C29.224576,24.4531669 29.0023717,23.7939849 28.4769698,23.5311325 C27.9518689,23.2682801 27.3228921,23.5014709 27.0720842,24.0517885" fill="#36301D" />
|
||||
<path id="Fill-15" d="M7.26939206,25.8461538 C5.48457391,25.8461538 4.03846154,24.5805233 4.03846154,23.0190896 C4.03846154,21.457656 5.48457391,20.1923077 7.26939206,20.1923077 C9.05324247,20.1923077 10.5,21.457656 10.5,23.0190896 C10.5,24.5805233 9.05324247,25.8461538 7.26939206,25.8461538" fill="#EB9260" />
|
||||
<path id="Fill-16" d="M37.1540074,25.8461538 C35.3691893,25.8461538 33.9230769,24.5805233 33.9230769,23.0190896 C33.9230769,21.457656 35.3691893,20.1923077 37.1540074,20.1923077 C38.9381804,20.1923077 40.3846154,21.457656 40.3846154,23.0190896 C40.3846154,24.5805233 38.9381804,25.8461538 37.1540074,25.8461538" fill="#EB9260" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit/Delete -->
|
||||
<div class="flex bt b--gray-monica">
|
||||
<div class="w-10">
|
||||
|
||||
</div>
|
||||
<div class="flex-none w-90 mt2 pt0 pr3 pb2">
|
||||
<ul class="f7">
|
||||
<li class="di">
|
||||
{{ $t('journal.journal_entry_rate') }}
|
||||
</li>
|
||||
<li class="di">
|
||||
<a v-cy-name="'entry-edit-button-' + journalEntry.id" class="pointer" :href="'journal/day/' + journalEntry.id + '/edit'" @click.prevent="editingComment">
|
||||
{{ $t('app.edit') }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="di">
|
||||
<a v-cy-name="'entry-delete-button-' + journalEntry.id" class="pointer" href="" @click.prevent="destroy()">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
journalEntry: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
day: [],
|
||||
isEditingComment: false,
|
||||
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.day = this.journalEntry.object;
|
||||
},
|
||||
editingComment() {
|
||||
this.isEditingComment = !this.isEditingComment;
|
||||
},
|
||||
saveComment() {
|
||||
axios.put('journal/day/' + this.day.id + '/update', {
|
||||
comment: this.day.comment,
|
||||
})
|
||||
.then(response => {
|
||||
this.editingComment();
|
||||
});
|
||||
},
|
||||
|
||||
destroy() {
|
||||
axios.delete('journal/day/' + this.day.id)
|
||||
.then(response => {
|
||||
this.$emit('deleteJournalEntry', this.journalEntry.id);
|
||||
});
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
58
resources/js/components/partials/Avatar.img.vue
Normal file
58
resources/js/components/partials/Avatar.img.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<style scoped>
|
||||
.avatar-padding {
|
||||
padding-top: 19%
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<img v-if="check"
|
||||
v-tooltip.bottom="contact.complete_name"
|
||||
:class="['br4 h3 w3 dib tc', imgclass]"
|
||||
:alt="contact.initials"
|
||||
:src="avatar_url"
|
||||
@error="check=false"
|
||||
/>
|
||||
<span v-else
|
||||
v-tooltip.bottom="contact.complete_name"
|
||||
:class="['br4 h3 w3 dib tc', 'white f3 avatar-padding', imgclass]"
|
||||
:style="'background-color: '+ default_avatar_color"
|
||||
>
|
||||
{{ contact.initials }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
contact: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
imgclass: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
check: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
avatar_url() {
|
||||
if (_.isObject(this.contact.information)) {
|
||||
return this.contact.information.avatar.url;
|
||||
}
|
||||
return this.contact.avatar_url;
|
||||
},
|
||||
default_avatar_color() {
|
||||
if (_.isObject(this.contact.information)) {
|
||||
return this.contact.information.avatar.default_avatar_color;
|
||||
}
|
||||
return this.contact.default_avatar_color;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
38
resources/js/components/partials/Avatar.vue
Normal file
38
resources/js/components/partials/Avatar.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="avatars">
|
||||
<a v-if="clickable === true" :href="'people/' + id">
|
||||
<avatarimg :contact="contact" :imgclass="imgclass" />
|
||||
</a>
|
||||
<avatarimg v-else :contact="contact" :imgclass="imgclass" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Avatarimg from './Avatar.img.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Avatarimg
|
||||
},
|
||||
|
||||
props: {
|
||||
contact: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
clickable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
imgclass: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
id() {
|
||||
return this.contact.hash_id ? this.contact.hash_id : this.contact.id;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
74
resources/js/components/partials/Confirm.vue
Normal file
74
resources/js/components/partials/Confirm.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="di">
|
||||
<a :class="lclass" :title="title" href="" @click.prevent="open">
|
||||
<slot>
|
||||
</slot>
|
||||
</a>
|
||||
|
||||
<sweet-modal ref="modal" tabindex="-1" role="dialog">
|
||||
<div>
|
||||
{{ message }}
|
||||
</div>
|
||||
|
||||
<!-- Modal Actions -->
|
||||
<div slot="button" class="flex-ns justify-between">
|
||||
<a class="btn mt2" href="" @click.prevent="close">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<button v-cy-name="'confirm-' + name" class="btn btn-primary w-auto-ns w100 mt2 pb0-ns" @click="confirm($event)">
|
||||
{{ $t('app.confirm') }}
|
||||
</button>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
SweetModal,
|
||||
},
|
||||
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
linkClass: {
|
||||
type: [String, Array],
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
lclass() {
|
||||
return this.linkClass === '' ? 'pointer' : this.linkClass;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
open() {
|
||||
this.$refs.modal.open();
|
||||
},
|
||||
close() {
|
||||
this.$refs.modal.close();
|
||||
},
|
||||
confirm(event) {
|
||||
this.close();
|
||||
this.$emit('confirm', event);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
||||
51
resources/js/components/partials/Error.vue
Normal file
51
resources/js/components/partials/Error.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div v-if="apierror || errors.length > 0" class="alert alert-danger">
|
||||
<p>{{ $t('app.error_title') }}</p>
|
||||
<template v-if="apierror">
|
||||
<ul v-if="apimessage">
|
||||
<li v-for="error in errors[0].message" :key="error.id">
|
||||
▪️ {{ error }}
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else>
|
||||
{{ errors[0].message }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p v-if="errors[0] !== 'The given data was invalid.'">
|
||||
{{ errors[0] }}
|
||||
</p>
|
||||
<template v-if="display(errors[1])">
|
||||
<ul v-for="errorsList in errors[1]" :key="errorsList.id">
|
||||
<li v-for="error in errorsList" :key="error.id">
|
||||
▪️ {{ error }}
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
errors: {
|
||||
type: [Array, Object],
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
apierror() {
|
||||
return _.isObject(this.errors[0]) && this.errors[0].error_code !== undefined;
|
||||
},
|
||||
apimessage() {
|
||||
return _.isArray(this.errors[0].message);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
display($val) {
|
||||
return _.isObject($val);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
255
resources/js/components/partials/SpecialDate.vue
Normal file
255
resources/js/components/partials/SpecialDate.vue
Normal file
@@ -0,0 +1,255 @@
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="pa4-ns ph3 pv2 bb b--gray-monica">
|
||||
<div class="mb3 mb0-ns">
|
||||
<form-radio
|
||||
v-model.lazy="selectedOption"
|
||||
:name="'birthdate'"
|
||||
:value="'unknown'"
|
||||
:dclass="'flex mb3'"
|
||||
:iclass="[ dirltr ? 'mr2' : 'ml2' ]"
|
||||
@change="event => { _saveOption(); }"
|
||||
>
|
||||
<template slot="label">
|
||||
{{ $t('people.information_edit_unknown') }}
|
||||
</template>
|
||||
</form-radio>
|
||||
<form-radio
|
||||
v-model.lazy="selectedOption"
|
||||
:name="'birthdate'"
|
||||
:value="'approximate'"
|
||||
:dclass="'flex mb3'"
|
||||
:iclass="[ dirltr ? 'mr2' : 'ml2' ]"
|
||||
@change="event => { if (selectedOptionSave !== 'approximate') {_focusAge();} _saveOption(); }"
|
||||
>
|
||||
<template slot="label">
|
||||
{{ $t('people.information_edit_probably') }}
|
||||
</template>
|
||||
<div v-if="selectedOption === 'approximate'" slot="extra">
|
||||
<form-input
|
||||
:id="'age'"
|
||||
ref="age"
|
||||
v-model="selectedAge"
|
||||
:input-type="'number'"
|
||||
:width="50"
|
||||
:required="true"
|
||||
:validator="$v.selectedAge"
|
||||
/>
|
||||
</div>
|
||||
</form-radio>
|
||||
<form-radio
|
||||
v-model.lazy="selectedOption"
|
||||
:name="'birthdate'"
|
||||
:value="'almost'"
|
||||
:dclass="'flex mb3'"
|
||||
:iclass="[ dirltr ? 'mr2' : 'ml2' ]"
|
||||
@change="event => { if (selectedOptionSave !== 'almost') {_focusMonth();} _saveOption(); }"
|
||||
>
|
||||
<template slot="label">
|
||||
{{ $t('people.information_edit_not_year') }}
|
||||
</template>
|
||||
<div v-if="selectedOption === 'almost'" slot="extra" class="mt2">
|
||||
<div class="flex">
|
||||
<form-select
|
||||
:id="'month'"
|
||||
ref="month"
|
||||
v-model="selectedMonth"
|
||||
:options="months"
|
||||
:title="''"
|
||||
:class="[ dirltr ? 'mr3' : '' ]"
|
||||
/>
|
||||
<form-select
|
||||
:id="'day'"
|
||||
v-model="selectedDay"
|
||||
:options="days"
|
||||
:title="''"
|
||||
:class="[ dirltr ? '' : 'mr3' ]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form-radio>
|
||||
<form-radio
|
||||
v-model.lazy="selectedOption"
|
||||
:name="'birthdate'"
|
||||
:value="'exact'"
|
||||
:dclass="'flex mb3'"
|
||||
:iclass="[ dirltr ? 'mr2' : 'ml2' ]"
|
||||
@change="event => { if (selectedOptionSave !== 'exact') {_focusBirthday();} _saveOption(); }"
|
||||
>
|
||||
<template slot="label">
|
||||
{{ $t('people.information_edit_exact') }}
|
||||
</template>
|
||||
<div v-if="selectedOption === 'exact'" slot="extra" class="mt2">
|
||||
<form-date
|
||||
:id="'birthdayDate'"
|
||||
ref="birthday"
|
||||
v-model="selectedDate"
|
||||
:show-calendar-on-focus="true"
|
||||
:locale="locale"
|
||||
:label="$t('people.information_edit_birthdate_label')"
|
||||
:class="[ dirltr ? 'fl' : 'fr' ]"
|
||||
:validator="$v.selectedDate"
|
||||
/>
|
||||
</div>
|
||||
</form-radio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedOption === 'exact' || selectedOption === 'almost'" class="pa4-ns ph3 pv2 bb b--gray-monica">
|
||||
<div class="mb2 mb0-ns">
|
||||
<form-checkbox
|
||||
v-model.lazy="hasBirthdayReminder"
|
||||
:name="'addReminder'"
|
||||
:value="'addReminder'"
|
||||
:dclass="[ 'flex', dirltr ? 'mr2' : 'ml2' ]"
|
||||
>
|
||||
<template slot="label">
|
||||
{{ $t('people.people_add_reminder_for_birthday') }}
|
||||
</template>
|
||||
</form-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { validationMixin } from 'vuelidate';
|
||||
import { required, numeric, helpers } from 'vuelidate/lib/validators';
|
||||
|
||||
const before = (param) =>
|
||||
helpers.withParams(
|
||||
{ type: 'before', date: param },
|
||||
(value) => !helpers.req(value) || moment(value).isBefore(param)
|
||||
);
|
||||
|
||||
export default {
|
||||
|
||||
mixins: [validationMixin],
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
days: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
months: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
day: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
month: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
birthdate: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
age: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
reminder: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedDate: null,
|
||||
selectedOption: null,
|
||||
selectedOptionSave: null,
|
||||
selectedAge: 0,
|
||||
selectedMonth: 0,
|
||||
selectedDay: 0,
|
||||
hasBirthdayReminder: false
|
||||
};
|
||||
},
|
||||
|
||||
validations() {
|
||||
switch (this.selectedOption) {
|
||||
case 'approximate':
|
||||
return {
|
||||
selectedAge: {
|
||||
required,
|
||||
numeric,
|
||||
}
|
||||
};
|
||||
case 'exact':
|
||||
return {
|
||||
selectedDate: {
|
||||
required,
|
||||
before: before(moment())
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
},
|
||||
locale() {
|
||||
return this.$root.locale;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
birthdate(val) {
|
||||
this.selectedDate = val;
|
||||
},
|
||||
|
||||
value(val) {
|
||||
this.selectedOption = val;
|
||||
},
|
||||
|
||||
age(val) {
|
||||
this.selectedAge = val;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.selectedDate = this.birthdate;
|
||||
this.selectedOption = this.value !== '' ? this.value : 'unknown';
|
||||
this.selectedOptionSave = this.selectedOption;
|
||||
this.selectedAge = this.age;
|
||||
this.selectedMonth = this.month;
|
||||
this.selectedDay = this.day;
|
||||
this.hasBirthdayReminder = this.reminder;
|
||||
},
|
||||
|
||||
methods: {
|
||||
_focusAge() {
|
||||
setTimeout(() => {
|
||||
this.$refs.age.focus();
|
||||
}, 100);
|
||||
},
|
||||
_focusMonth() {
|
||||
setTimeout(() => {
|
||||
this.$refs.month.focus();
|
||||
}, 100);
|
||||
},
|
||||
_focusBirthday() {
|
||||
setTimeout(() => {
|
||||
this.$refs.birthday.focus();
|
||||
}, 100);
|
||||
},
|
||||
_saveOption() {
|
||||
this.selectedOptionSave = this.selectedOption;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
133
resources/js/components/partials/SpecialDeceased.vue
Normal file
133
resources/js/components/partials/SpecialDeceased.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="pa4-ns ph3 pv2 bb b--gray-monica">
|
||||
<div class="mb3 mb0-ns">
|
||||
<form-checkbox
|
||||
v-model.lazy="deceased"
|
||||
:name="'is_deceased'"
|
||||
:value="true"
|
||||
:dclass="'flex mb2'"
|
||||
>
|
||||
<template slot="label">
|
||||
{{ $t('people.deceased_mark_person_deceased') }}
|
||||
</template>
|
||||
</form-checkbox>
|
||||
<div v-show="deceased" :class="[ dirltr ? 'ml4' : 'mr4' ]">
|
||||
<form-checkbox
|
||||
v-model.lazy="dateKnown"
|
||||
:name="'is_deceased_date_known'"
|
||||
:value="true"
|
||||
:dclass="'flex mb1'"
|
||||
@change="_focusDate()"
|
||||
>
|
||||
<template slot="label">
|
||||
{{ $t('people.deceased_know_date') }}
|
||||
</template>
|
||||
</form-checkbox>
|
||||
<div v-show="dateKnown" :class="[ dirltr ? 'ml4' : 'mr4' ]">
|
||||
<form-date
|
||||
:id="'deceased_date'"
|
||||
ref="deaceasedday"
|
||||
v-model="selectedDate"
|
||||
:label="$t('people.deceased_date_label')"
|
||||
:show-calendar-on-focus="true"
|
||||
:locale="locale"
|
||||
:validator="$v.selectedDate"
|
||||
/>
|
||||
<div v-show="selectedDate !== ''" class="mt2">
|
||||
<form-checkbox
|
||||
:name="'add_reminder_deceased'"
|
||||
:value="true"
|
||||
:model="reminder"
|
||||
>
|
||||
{{ $t('people.deceased_add_reminder') }}
|
||||
</form-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { validationMixin } from 'vuelidate';
|
||||
import { required, numeric, helpers } from 'vuelidate/lib/validators';
|
||||
|
||||
const before = (param) =>
|
||||
helpers.withParams(
|
||||
{ type: 'before', date: param },
|
||||
(value) => !helpers.req(value) || moment(value).isBefore(param)
|
||||
);
|
||||
|
||||
export default {
|
||||
|
||||
mixins: [validationMixin],
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
date: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
reminder: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
deceased: false,
|
||||
dateKnown: false,
|
||||
selectedDate: null,
|
||||
};
|
||||
},
|
||||
|
||||
validations: {
|
||||
selectedDate: {
|
||||
required,
|
||||
before: before(moment())
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
},
|
||||
locale() {
|
||||
return this.$root.locale;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(val) {
|
||||
this.deceased = val;
|
||||
},
|
||||
|
||||
date(val) {
|
||||
this.selectedDate = val;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.deceased = this.value;
|
||||
this.dateKnown = this.date !== '';
|
||||
this.selectedDate = this.date;
|
||||
},
|
||||
|
||||
methods: {
|
||||
_focusDate() {
|
||||
setTimeout(() => {
|
||||
this.$refs.deaceasedday.focus();
|
||||
}, 100);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
23
resources/js/components/partials/form/Checkbox.vue
Normal file
23
resources/js/components/partials/form/Checkbox.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script>
|
||||
const input = require('./PInput').default;
|
||||
|
||||
const checkbox = {
|
||||
name: 'checkbox',
|
||||
|
||||
input_type: 'checkbox',
|
||||
input_iclass: 'p-curve p-thick',
|
||||
|
||||
components: input.components,
|
||||
model: input.model,
|
||||
props: input.props,
|
||||
data: input.data,
|
||||
computed: input.computed,
|
||||
watch: input.watch,
|
||||
mounted: input.mounted,
|
||||
methods: input.methods,
|
||||
|
||||
render: input.render,
|
||||
};
|
||||
|
||||
export default checkbox;
|
||||
</script>
|
||||
206
resources/js/components/partials/form/Date.vue
Normal file
206
resources/js/components/partials/form/Date.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<div :class="{ 'form-group-error': validator && validator.$error }">
|
||||
<datepicker
|
||||
ref="select"
|
||||
:ref-name="'select'"
|
||||
:value="selectedDate"
|
||||
:format="displayValue"
|
||||
:parse-typed-date="formatTypedValue"
|
||||
:language="locale"
|
||||
:monday-first="mondayFirst"
|
||||
:input-class="inputClass"
|
||||
:typeable="true"
|
||||
:clear-button="true"
|
||||
:show-calendar-on-focus="showCalendarOnFocus"
|
||||
@input="onInput($event)"
|
||||
@selected="onSelected($event)"
|
||||
@clearDate="onSelected('')"
|
||||
/>
|
||||
<input :name="id" type="hidden" :value="exchange" />
|
||||
<small v-if="validator && (validator.$error && validator.required !== undefined && !validator.required)" class="error">
|
||||
{{ requiredMessage }}
|
||||
</small>
|
||||
<small v-if="validator && (validator.$error && validator.before !== undefined && !validator.before)" class="error">
|
||||
{{ beforeMessage }}
|
||||
</small>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Datepicker from '@hokify/vuejs-datepicker';
|
||||
import moment from 'moment';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
Datepicker
|
||||
},
|
||||
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'input'
|
||||
},
|
||||
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
defaultDate: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
locale: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
showCalendarOnFocus: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
validator: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
/**
|
||||
* Value of the date in exchange format
|
||||
*/
|
||||
exchange: '',
|
||||
|
||||
selectedDate: '',
|
||||
mondayFirst: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* Exchange format with controller (moment format type).
|
||||
*/
|
||||
exchangeFormat() {
|
||||
return 'YYYY-MM-DD';
|
||||
},
|
||||
|
||||
/**
|
||||
* Display format (moment format type).
|
||||
*/
|
||||
displayFormat() {
|
||||
return 'L';
|
||||
},
|
||||
|
||||
inputClass() {
|
||||
var c = ['br2 f5 ba b--black-40 pa2 outline-0'];
|
||||
if (this.validator) {
|
||||
c.push({ 'error': this.validator.$error });
|
||||
}
|
||||
return c;
|
||||
},
|
||||
|
||||
requiredMessage() {
|
||||
return this.$t('validation.vue.required', { field: this.label });
|
||||
},
|
||||
|
||||
beforeMessage() {
|
||||
return this.$t('validation.vue.max.numeric', {
|
||||
field: this.label,
|
||||
max: this.displayValue(this.validator.$params.before.date)
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: function (newValue) {
|
||||
this.updateExchange(newValue);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.updateExchange(this.value === '' ? this.defaultDate : this.value);
|
||||
this.mondayFirst = moment.localeData().firstDayOfWeek() === 1;
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Format date for display it.
|
||||
* @param date string in locale format
|
||||
* @return string date in display format
|
||||
*/
|
||||
displayValue(date) {
|
||||
return date !== '' && date !== null ? moment(date).format(this.displayFormat) : '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Format date for save it.
|
||||
* @param date string in locale format
|
||||
* @return string date in exchange format
|
||||
*/
|
||||
exchangeValue(date) {
|
||||
return date !== '' && date !== null ? moment(date).format(this.exchangeFormat) : '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the value of hidden input.
|
||||
* Store it in exchange format value.
|
||||
*/
|
||||
update(date) {
|
||||
if (date === '' || date === null) {
|
||||
this.exchange = '';
|
||||
} else {
|
||||
var mdate = moment(date);
|
||||
if (! mdate.isValid()) {
|
||||
mdate = moment();
|
||||
}
|
||||
this.exchange = mdate.format(this.exchangeFormat);
|
||||
}
|
||||
},
|
||||
|
||||
updateExchange(date) {
|
||||
this.exchange = date;
|
||||
if (this.exchange !== '') {
|
||||
var mdate = moment(this.exchange, this.exchangeFormat);
|
||||
if (! mdate.isValid()) {
|
||||
mdate = moment();
|
||||
}
|
||||
this.selectedDate = mdate.toDate();
|
||||
}
|
||||
this.update(this.selectedDate);
|
||||
},
|
||||
|
||||
/**
|
||||
* Format the typed value with the locale specification.
|
||||
* @param date string in locale format
|
||||
* @return date value
|
||||
*/
|
||||
formatTypedValue(date) {
|
||||
return date !== '' && date !== null ? moment(date, this.displayFormat).toDate() : '';
|
||||
},
|
||||
|
||||
focus() {
|
||||
this.$refs.select.$children[0].$refs.select.focus();
|
||||
},
|
||||
|
||||
onInput(event) {
|
||||
if (this.validator) {
|
||||
this.validator.$touch();
|
||||
}
|
||||
this.$emit('input', this.exchangeValue(event));
|
||||
},
|
||||
|
||||
onSelected(event) {
|
||||
this.update(event);
|
||||
this.onInput(event);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
181
resources/js/components/partials/form/Input.vue
Normal file
181
resources/js/components/partials/form/Input.vue
Normal file
@@ -0,0 +1,181 @@
|
||||
<style scoped>
|
||||
.input {
|
||||
transition: all;
|
||||
transition-duration: 0.2s;
|
||||
border: 1px solid #c4cdd5;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
border: 1px solid #5c6ac4;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div :class="{ 'form-group-error': validator && validator.$error }">
|
||||
<label
|
||||
v-if="title"
|
||||
:for="realid"
|
||||
class="mb2"
|
||||
:class="{ b: required, error: validator && validator.$error }"
|
||||
>
|
||||
{{ title }}
|
||||
</label>
|
||||
<input
|
||||
:id="realid"
|
||||
ref="input"
|
||||
:type="inputType"
|
||||
autofocus
|
||||
:required="required"
|
||||
:name="id"
|
||||
:placeholder="placeholder"
|
||||
:class="inputClass"
|
||||
:style="inputStyle"
|
||||
:value="value"
|
||||
:maxlength="maxlength"
|
||||
:step="step"
|
||||
@input="onInput($event)"
|
||||
@blur="onBlur($event)"
|
||||
@change="onChange($event)"
|
||||
@keyup.enter="onSubmit($event)"
|
||||
/>
|
||||
<small v-if="validator && (validator.$error && validator.required !== undefined && !validator.required)" class="error">
|
||||
{{ requiredMessage }}
|
||||
</small>
|
||||
<small v-if="validator && (validator.$error && validator.maxLength !== undefined && !validator.maxLength)" class="error">
|
||||
{{ maxLengthMessage }}
|
||||
</small>
|
||||
<small v-if="validator && (validator.$error && validator.url !== undefined && !validator.url)" class="error">
|
||||
{{ urlMessage }}
|
||||
</small>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
inputType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
step: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
iclass: {
|
||||
type: [String, Array],
|
||||
default: ''
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
validator: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
realid() {
|
||||
return this.id + this._uid;
|
||||
},
|
||||
inputClass() {
|
||||
var c = [this.iclass !== '' ? this.iclass : 'br2 f5 w-100 ba b--black-40 pa2 outline-0'];
|
||||
if (this.validator) {
|
||||
c.push({ error: this.validator.$error });
|
||||
}
|
||||
c.push('input');
|
||||
return c;
|
||||
},
|
||||
inputStyle() {
|
||||
return this.width >= 0 ? 'width:' + this.width + 'px' : '';
|
||||
},
|
||||
|
||||
field() {
|
||||
return this.label && this.label.length > 0 ? this.label : this.title;
|
||||
},
|
||||
requiredMessage() {
|
||||
return this.$t('validation.vue.required', { field: this.field });
|
||||
},
|
||||
urlMessage() {
|
||||
return this.$t('validation.vue.url', { field: this.field });
|
||||
},
|
||||
maxLengthMessage() {
|
||||
var type = 'string';
|
||||
switch (this.inputType) {
|
||||
case 'number':
|
||||
type = 'numeric';
|
||||
break;
|
||||
}
|
||||
return this.$t(`validation.vue.max.${type}`, {
|
||||
field: this.field,
|
||||
max: this.validator ? this.validator.$params.maxLength.max : '',
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
focus() {
|
||||
this.$refs.input.focus();
|
||||
},
|
||||
|
||||
onInput(event) {
|
||||
if (this.validator && event.data !== undefined) {
|
||||
this.validator.$reset();
|
||||
}
|
||||
this.$emit('input', event.target.value);
|
||||
},
|
||||
|
||||
onSubmit(event) {
|
||||
if (this.validator) {
|
||||
this.validator.$touch();
|
||||
}
|
||||
this.$emit('submit', event.target.value);
|
||||
},
|
||||
|
||||
onBlur(event) {
|
||||
if (this.validator && event.target.value !== '') {
|
||||
this.validator.$touch();
|
||||
}
|
||||
this.$emit('blur', event.target.value);
|
||||
},
|
||||
|
||||
onChange(event) {
|
||||
if (this.validator) {
|
||||
this.validator.$touch();
|
||||
}
|
||||
this.$emit('change', event.target.value);
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
137
resources/js/components/partials/form/PInput.vue
Normal file
137
resources/js/components/partials/form/PInput.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div :class="dclass">
|
||||
<div>
|
||||
<p-input
|
||||
ref="input"
|
||||
v-model.lazy="prop"
|
||||
:name="name"
|
||||
:type="_type"
|
||||
:class="inputClass"
|
||||
:color="inputColor"
|
||||
:value="value"
|
||||
:disabled="disabled"
|
||||
:required="required"
|
||||
@change="$emit('change', $event)"
|
||||
>
|
||||
<slot></slot>
|
||||
<slot slot="extra" name="inputextra"></slot>
|
||||
</p-input>
|
||||
</div>
|
||||
<div class="pointer" @click="select()">
|
||||
<label v-if="hasSlot('label')" class="pointer">
|
||||
<slot name="label"></slot>
|
||||
</label>
|
||||
<slot name="extra"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PInput from 'pretty-checkbox-vue/input';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
PInput
|
||||
},
|
||||
|
||||
model: {
|
||||
prop: 'modelValue',
|
||||
event: 'change'
|
||||
},
|
||||
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: [String, Boolean],
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Boolean],
|
||||
default: '',
|
||||
},
|
||||
iclass: {
|
||||
type: [String, Array],
|
||||
default: ''
|
||||
},
|
||||
fullClass: {
|
||||
type: [String, Array],
|
||||
default: ''
|
||||
},
|
||||
dclass: {
|
||||
type: [String, Array],
|
||||
default: ''
|
||||
},
|
||||
color: {
|
||||
type: [String, Array],
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
prop: null
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
_type() {
|
||||
if (this.$options.input_type) {
|
||||
return this.$options.input_type;
|
||||
}
|
||||
return 'input';
|
||||
},
|
||||
inputClass() {
|
||||
return this.fullClass !== '' ? this.fullClass : [this.iclass, 'p-default', this.$options.input_iclass];
|
||||
},
|
||||
inputColor() {
|
||||
return this.color !== '' ? this.color : 'primary-o';
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
modelValue(val) {
|
||||
this.prop = val;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prop = this.modelValue;
|
||||
},
|
||||
|
||||
methods: {
|
||||
select() {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
switch (this._type)
|
||||
{
|
||||
case 'checkbox':
|
||||
this.$refs.input.$refs.input.checked = ! this.$refs.input.$refs.input.checked;
|
||||
this.$emit('change', this.$refs.input.$refs.input.checked);
|
||||
break;
|
||||
case 'radio':
|
||||
this.$refs.input.$refs.input.checked = true;
|
||||
this.$emit('change', this.value);
|
||||
break;
|
||||
case 'input':
|
||||
//this.$refs.input.$refs.input.focus();
|
||||
}
|
||||
},
|
||||
hasSlot (name = 'default') {
|
||||
return !!this.$slots[ name ] || !!this.$scopedSlots[ name ];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
23
resources/js/components/partials/form/Radio.vue
Normal file
23
resources/js/components/partials/form/Radio.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script>
|
||||
const input = require('./PInput').default;
|
||||
|
||||
const radio = {
|
||||
name: 'radio',
|
||||
|
||||
input_type: 'radio',
|
||||
input_iclass: 'p-round p-thick',
|
||||
|
||||
components: input.components,
|
||||
model: input.model,
|
||||
props: input.props,
|
||||
data: input.data,
|
||||
computed: input.computed,
|
||||
watch: input.watch,
|
||||
mounted: input.mounted,
|
||||
methods: input.methods,
|
||||
|
||||
render: input.render,
|
||||
};
|
||||
|
||||
export default radio;
|
||||
</script>
|
||||
164
resources/js/components/partials/form/Select.vue
Normal file
164
resources/js/components/partials/form/Select.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<style scoped>
|
||||
.select {
|
||||
height: 34px;
|
||||
transition: all;
|
||||
transition-duration: 0.2s;
|
||||
border: 1px solid #c4cdd5;
|
||||
}
|
||||
.select:focus {
|
||||
border: 1px solid #5c6ac4;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div :class="{ 'form-group-error': validator && validator.$error }">
|
||||
<label
|
||||
v-if="title"
|
||||
:for="realid"
|
||||
class="mb2"
|
||||
:class="{ b: required, error: validator && validator.$error }"
|
||||
>
|
||||
{{ title }}
|
||||
</label>
|
||||
<select
|
||||
:id="realid"
|
||||
ref="select"
|
||||
:value="selectedOption"
|
||||
:name="id"
|
||||
:required="required"
|
||||
:class="selectClass"
|
||||
@input="onInput"
|
||||
>
|
||||
<template v-if="Array.isArray(options)">
|
||||
<option
|
||||
v-for="option in filterExclude(options)"
|
||||
:key="option.id"
|
||||
:value="option.id"
|
||||
>
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</template>
|
||||
<template v-else>
|
||||
<optgroup
|
||||
v-for="(optgroup,index) in options"
|
||||
:key="index"
|
||||
:label="optgroup.name"
|
||||
>
|
||||
<option
|
||||
v-for="option in filterExclude(optgroup.options)"
|
||||
:key="option.id"
|
||||
:value="option.id"
|
||||
>
|
||||
{{ option.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</template>
|
||||
</select>
|
||||
<small v-if="validator && (validator.$error && validator.required !== undefined && !validator.required)" class="error">
|
||||
{{ requiredMessage }}
|
||||
</small>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
options: {
|
||||
type: [Array, Object],
|
||||
default: () => [],
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
excludedId: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
iclass: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
validator: {
|
||||
type: Object,
|
||||
default: null,
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedOption: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
realid() {
|
||||
return this.id + this._uid;
|
||||
},
|
||||
selectClass() {
|
||||
var c = [this.iclass !== '' ? this.iclass : 'br2 f5 w-100 ba b--black-40 pa2 outline-0'];
|
||||
if (this.validator) {
|
||||
c.push({ error: this.validator.$error });
|
||||
}
|
||||
c.push('select');
|
||||
return c;
|
||||
},
|
||||
field() {
|
||||
return this.label && this.label.length > 0 ? this.label : this.title;
|
||||
},
|
||||
requiredMessage() {
|
||||
return this.$t('validation.vue.required', { field: this.field });
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: function (newValue) {
|
||||
this.selectedOption = newValue;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.selectedOption = this.value;
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Filter options
|
||||
*/
|
||||
filterExclude: function (options) {
|
||||
var me = this;
|
||||
return options.filter(function (option) {
|
||||
return option.id !== me.excludedId;
|
||||
});
|
||||
},
|
||||
|
||||
focus() {
|
||||
this.$refs.select.focus();
|
||||
},
|
||||
|
||||
onInput(event) {
|
||||
if (this.validator) {
|
||||
this.validator.$touch();
|
||||
}
|
||||
this.$emit('input', event.target.value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
96
resources/js/components/partials/form/Textarea.vue
Normal file
96
resources/js/components/partials/form/Textarea.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<style scoped>
|
||||
textarea {
|
||||
transition: all;
|
||||
transition-duration: 0.2s;
|
||||
border: 1px solid #c4cdd5;
|
||||
}
|
||||
textarea:focus {
|
||||
border: 1px solid #5c6ac4;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<label
|
||||
v-if="label"
|
||||
:for="realid"
|
||||
class="mb2"
|
||||
:class="{ b: required }"
|
||||
>
|
||||
{{ label }}
|
||||
</label>
|
||||
<textarea
|
||||
:id="realid"
|
||||
v-model="buffer"
|
||||
autofocus
|
||||
:required="required"
|
||||
:name="id"
|
||||
:placeholder="placeholder"
|
||||
:rows="rows"
|
||||
class="br2 f5 w-100 ba b--black-40 pa2 outline-0"
|
||||
:style="textareaStyle"
|
||||
@input="$emit('input', buffer)"
|
||||
></textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
buffer: this.value
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
realid() {
|
||||
return this.id + this._uid;
|
||||
},
|
||||
textareaStyle() {
|
||||
return this.width >= 0 ? 'width:' + this.width + 'px' : '';
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: function (newValue) {
|
||||
this.buffer = newValue;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.buffer = this.value;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
91
resources/js/components/partials/form/Toggle.vue
Normal file
91
resources/js/components/partials/form/Toggle.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div>
|
||||
<label
|
||||
v-if="title"
|
||||
:for="realId"
|
||||
class="mb2"
|
||||
:class="{ b: required }"
|
||||
>
|
||||
{{ title }}
|
||||
</label>
|
||||
<toggle-button
|
||||
:id="realId"
|
||||
:name="id"
|
||||
:class="inputClass"
|
||||
:sync="true"
|
||||
:labels="labels"
|
||||
:disabled="disabled"
|
||||
:value="selectedOption"
|
||||
@input="$emit('input', $event)"
|
||||
@change="$emit('change', $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ToggleButton } from 'vue-js-toggle-button';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
ToggleButton
|
||||
},
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
labels: {
|
||||
type: [Boolean, Object],
|
||||
default: false,
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
iclass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedOption: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
realId() {
|
||||
return this.id + this._uid;
|
||||
},
|
||||
inputClass() {
|
||||
return this.iclass !== '' ? this.iclass : '';
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: function (newValue) {
|
||||
this.selectedOption = newValue;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.selectedOption = this.value;
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
96
resources/js/components/passport/AuthorizedClients.vue
Normal file
96
resources/js/components/passport/AuthorizedClients.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3 class="mb3">
|
||||
{{ $t('settings.api_authorized_clients') }}
|
||||
</h3>
|
||||
<p>{{ $t('settings.api_authorized_clients_desc') }}</p>
|
||||
|
||||
<!-- Authorized Clients -->
|
||||
<p v-if="tokens.length === 0" class="mb0">
|
||||
{{ $t('settings.api_authorized_clients_none') }}
|
||||
</p>
|
||||
|
||||
<div v-else class="dt dt--fixed w-100 collapse br--top br--bottom">
|
||||
<em>{{ $t('settings.api_authorized_clients_title') }}</em>
|
||||
<div class="dt-row">
|
||||
<div class="dtc w-20">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.api_authorized_clients_name') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc w-20">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.api_authorized_clients_scopes') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_contact_field_type_table_actions') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="token in tokens" :key="token.id" class="dt-row bb b--light-gray">
|
||||
<!-- Client Name -->
|
||||
<div class="dtc">
|
||||
<div class="pa2">
|
||||
{{ token.client.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scopes -->
|
||||
<div class="dtc">
|
||||
<div class="pa2">
|
||||
<span v-if="token.scopes.length > 0">
|
||||
{{ token.scopes.join(', ') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Revoke Button -->
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2">
|
||||
<span class="pointer" @click="revoke(token)">{{ $t('app.revoke') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
tokens: []
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getTokens();
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Get all of the authorized tokens for the user.
|
||||
*/
|
||||
getTokens() {
|
||||
axios.get('oauth/tokens')
|
||||
.then(response => {
|
||||
this.tokens = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Revoke the given token.
|
||||
*/
|
||||
revoke(token) {
|
||||
axios.delete('oauth/tokens/' + token.id)
|
||||
.then(response => {
|
||||
this.getTokens();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
316
resources/js/components/passport/Clients.vue
Normal file
316
resources/js/components/passport/Clients.vue
Normal file
@@ -0,0 +1,316 @@
|
||||
<template>
|
||||
<div>
|
||||
<notifications group="passport-clients" position="top middle" :duration="5000" width="400" />
|
||||
|
||||
<h3 class="mb3">
|
||||
{{ $t('settings.api_oauth_clients') }}
|
||||
<a class="btn nt2" :class="[ dirltr ? 'fr' : 'fl' ]" href="" @click.prevent="showCreateClientForm">
|
||||
{{ $t('settings.api_oauth_create_new') }}
|
||||
</a>
|
||||
</h3>
|
||||
<p>{{ $t('settings.api_oauth_clients_desc') }}</p>
|
||||
<p v-html="$t('settings.api_oauth_clients_desc2', { url: 'https://laravel.com/docs/master/passport#requesting-tokens' })"></p>
|
||||
|
||||
<!-- Current Clients -->
|
||||
<p v-if="clients.length === 0" class="mb0">
|
||||
{{ $t('settings.api_oauth_not_created') }}
|
||||
</p>
|
||||
|
||||
<div v-else class="dt w-100 collapse br--top br--bottom">
|
||||
<em>{{ $t('settings.api_oauth_title') }}</em>
|
||||
<div class="dt-row">
|
||||
<div class="dtc w-20">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.api_oauth_clientid') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc w-20">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.api_oauth_name') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.api_oauth_secret') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_contact_field_type_table_actions') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="client in clients" :key="client.id" class="dt-row bb b--light-gray">
|
||||
<!-- ID -->
|
||||
<div class="dtc">
|
||||
<div class="pa2">
|
||||
{{ client.id }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Name -->
|
||||
<div class="dtc">
|
||||
<div class="pa2">
|
||||
{{ client.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Secret -->
|
||||
<div class="dtc">
|
||||
<div class="pa2 flex flex-auto">
|
||||
<code dir="ltr">{{ client.secret }}</code>
|
||||
<em class="fa fa-clipboard pointer" :class="[ dirltr ? 'ml2' : 'mr2' ]"
|
||||
:title="$t('settings.dav_copy_help')"
|
||||
@click="copyIntoClipboard(client.secret)"
|
||||
></em>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2">
|
||||
<em class="fa fa-pencil-square-o pointer pr2" @click="edit(client)"></em>
|
||||
<em class="fa fa-trash-o pointer" @click="destroy(client)"></em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Client Modal -->
|
||||
<sweet-modal ref="modalClient" overlay-theme="dark" tabindex="-1" role="dialog"
|
||||
:title="form.id ? $t('settings.api_oauth_edit') : $t('settings.api_oauth_create')"
|
||||
@open="_focusInput"
|
||||
>
|
||||
<!-- Form Errors -->
|
||||
<errors :errors="form.errors" />
|
||||
|
||||
<!-- Create Client Form -->
|
||||
<form ref="form" class="form-horizontal" role="form">
|
||||
<!-- Name -->
|
||||
<div class="form-group">
|
||||
<div class="col-md-auto">
|
||||
<form-input
|
||||
:id="'client-name'"
|
||||
ref="clientName"
|
||||
v-model="form.name"
|
||||
:iclass="'br2 f5 w-50 ba b--black-40 pa2 outline-0'"
|
||||
:required="true"
|
||||
:title="$t('settings.api_oauth_name')"
|
||||
:validator="$v.form.name"
|
||||
@submit="store"
|
||||
/>
|
||||
|
||||
<span class="help-block">
|
||||
{{ $t('settings.api_oauth_name_help') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Redirect URL -->
|
||||
<div class="form-group">
|
||||
<div class="col-md-auto">
|
||||
<form-input
|
||||
:id="'redirect-url'"
|
||||
v-model="form.redirect"
|
||||
:iclass="'br2 f5 w-50 ba b--black-40 pa2 outline-0'"
|
||||
:required="true"
|
||||
:title="$t('settings.api_oauth_redirecturl')"
|
||||
:validator="$v.form.redirect"
|
||||
@submit="store"
|
||||
/>
|
||||
|
||||
<span class="help-block">
|
||||
{{ $t('settings.api_oauth_redirecturl_help') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Modal Actions -->
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeModal">
|
||||
{{ $t('app.close') }}
|
||||
</a>
|
||||
<a class="btn btn-primary" href="" @click.prevent="store">
|
||||
{{ form.id ? $t('app.save') : $t('app.create') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Errors from '../partials/Error.vue';
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
import { validationMixin } from 'vuelidate';
|
||||
import { required, url } from 'vuelidate/lib/validators';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
SweetModal,
|
||||
Errors,
|
||||
},
|
||||
|
||||
mixins: [validationMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
clients: [],
|
||||
|
||||
form: {
|
||||
errors: [],
|
||||
name: '',
|
||||
redirect: ''
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
validations: {
|
||||
form: {
|
||||
name: {
|
||||
required,
|
||||
},
|
||||
redirect: {
|
||||
required,
|
||||
url,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getClients();
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus on modal open.
|
||||
*/
|
||||
_focusInput() {
|
||||
const vm = this;
|
||||
setTimeout(function() {
|
||||
vm.$refs.clientName.focus();
|
||||
}, 10);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all of the OAuth clients for the user.
|
||||
*/
|
||||
getClients() {
|
||||
axios.get('oauth/clients')
|
||||
.then(response => {
|
||||
this.clients = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the form for creating new clients.
|
||||
*/
|
||||
showCreateClientForm() {
|
||||
this.resetField();
|
||||
this.$refs.modalClient.open();
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new OAuth client for the user.
|
||||
*/
|
||||
store() {
|
||||
this.$v.$touch();
|
||||
|
||||
if (this.$v.$invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const method = this.form.id ? 'put' : 'post';
|
||||
const url = this.form.id ? 'oauth/clients/' + this.form.id : 'oauth/clients';
|
||||
|
||||
this.persistClient(method, url, this.form);
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit the given client.
|
||||
*/
|
||||
edit(client) {
|
||||
this.form = Object.assign({errors:[]}, client);
|
||||
|
||||
this.$refs.modalClient.open();
|
||||
},
|
||||
|
||||
/**
|
||||
* Persist the client to storage using the given form.
|
||||
*/
|
||||
persistClient(method, uri, form) {
|
||||
form.errors = [];
|
||||
|
||||
axios[method](uri, form)
|
||||
.then(response => {
|
||||
this.getClients();
|
||||
|
||||
this.closeModal();
|
||||
})
|
||||
.catch(error => {
|
||||
if (typeof error.response.data === 'object') {
|
||||
form.errors = _.flatten(_.toArray(error.response.data));
|
||||
} else {
|
||||
form.errors = [this.$t('app.error_try_again')];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the given client.
|
||||
*/
|
||||
destroy(client) {
|
||||
axios.delete('oauth/clients/' + client.id)
|
||||
.then(response => {
|
||||
this.getClients();
|
||||
});
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
this.resetField();
|
||||
this.$v.$reset();
|
||||
this.$refs.form.reset();
|
||||
this.$refs.modalClient.close();
|
||||
},
|
||||
|
||||
resetField() {
|
||||
this.form = {
|
||||
id: '',
|
||||
errors:[],
|
||||
name: '',
|
||||
redirect: '',
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy text into clipboard
|
||||
*/
|
||||
copyIntoClipboard(text) {
|
||||
this.$copyText(text)
|
||||
.then(response => {
|
||||
this.notify(this.$t('settings.dav_clipboard_copied'), true);
|
||||
});
|
||||
},
|
||||
|
||||
notify(text, success) {
|
||||
this.$notify({
|
||||
group: 'passport-clients',
|
||||
title: text,
|
||||
text: '',
|
||||
type: success ? 'success' : 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
338
resources/js/components/passport/PersonalAccessTokens.vue
Normal file
338
resources/js/components/passport/PersonalAccessTokens.vue
Normal file
@@ -0,0 +1,338 @@
|
||||
<style scoped>
|
||||
|
||||
.access-key {
|
||||
border: 1px solid #cacaca;
|
||||
border-radius: 3px;
|
||||
padding: 10px 10px 0;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-size: 12px;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3 class="mb3">
|
||||
{{ $t('settings.api_personal_access_tokens') }}
|
||||
<a class="btn nt2" :class="[ dirltr ? 'fr' : 'fl' ]" href="" @click.prevent="showCreateTokenForm">
|
||||
{{ $t('settings.api_token_create_new') }}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<p>{{ $t('settings.api_pao_description') }}</p>
|
||||
|
||||
<!-- No Tokens Notice -->
|
||||
<p v-if="tokens.length === 0" class="mb0">
|
||||
{{ $t('settings.api_token_not_created') }}
|
||||
</p>
|
||||
|
||||
<div v-else class="dt w-75 collapse br--top br--bottom">
|
||||
<em>{{ $t('settings.api_token_title') }}</em>
|
||||
<div class="dt-row">
|
||||
<div class="dtc">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.api_token_name') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_contact_field_type_table_actions') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="token in tokens" :key="token.id" class="dt-row bb b--light-gray">
|
||||
<!-- Client Name -->
|
||||
<div class="dtc">
|
||||
<div v-tooltip="$t('settings.api_token_expire', { date: token.expires_at })" class="pa2">
|
||||
{{ token.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2">
|
||||
<span class="pointer" @click="revoke(token)">{{ $t('app.delete') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Token Modal -->
|
||||
<sweet-modal ref="modalCreateToken" overlay-theme="dark" tabindex="-1" role="dialog"
|
||||
:title="$t('settings.api_token_create')" @open="_focusInput"
|
||||
>
|
||||
<!-- Form Errors -->
|
||||
<errors :errors="form.errors" />
|
||||
|
||||
<!-- Create Token Form -->
|
||||
<form ref="form" class="form-horizontal" role="form" @submit.prevent="store">
|
||||
<!-- Name -->
|
||||
<div class="col-md-auto">
|
||||
<form-input
|
||||
:id="'create-token-name'"
|
||||
ref="createTokenName"
|
||||
v-model="form.name"
|
||||
:name="'name'"
|
||||
:iclass="'br2 f5 w-50 ba b--black-40 pa2 outline-0'"
|
||||
:required="true"
|
||||
:title="$t('settings.api_token_name')"
|
||||
:validator="$v.form.name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Scopes -->
|
||||
<div v-if="scopes.length > 0" class="form-group">
|
||||
<label class="col-md-4 col-form-label">
|
||||
{{ $t('settings.api_token_scopes') }}
|
||||
</label>
|
||||
|
||||
<div class="col-md-auto">
|
||||
<div v-for="scope in scopes" :key="scope.id">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
:checked="scopeIsAssigned(scope.id)"
|
||||
@click="toggleScope(scope.id)"
|
||||
/>
|
||||
|
||||
{{ scope.id }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<!-- Modal Actions -->
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeModal">
|
||||
{{ $t('app.close') }}
|
||||
</a>
|
||||
<a class="btn btn-primary" href="" @click.prevent="store">
|
||||
{{ $t('app.create') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
|
||||
<!-- Access Token Modal -->
|
||||
<sweet-modal ref="modalAccessToken" overlay-theme="dark" tabindex="-1" role="dialog" :title="$t('settings.api_token_title')">
|
||||
<notifications group="passport-personal-access-token" position="middle" :duration="5000" width="400" />
|
||||
<p>{{ $t('settings.api_token_help') }}</p>
|
||||
|
||||
<div class="flex-auto access-key overflow-y-scroll" style="max-height: 400px;" @click.prevent="copyIntoClipboard(accessToken)">
|
||||
<pre><code>{{ accessToken }}</code></pre>
|
||||
</div>
|
||||
|
||||
<!-- Modal Actions -->
|
||||
<div slot="button">
|
||||
<a class="btn btn-primary" :title="$t('settings.dav_copy_help')" href="" @click.prevent="copyIntoClipboard(accessToken)">
|
||||
{{ $t('app.copy') }}
|
||||
</a>
|
||||
<a class="btn" href="" @click.prevent="closeModal">
|
||||
{{ $t('app.close') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Errors from '../partials/Error.vue';
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
import { validationMixin } from 'vuelidate';
|
||||
import { required } from 'vuelidate/lib/validators';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
SweetModal,
|
||||
Errors,
|
||||
},
|
||||
|
||||
mixins: [validationMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
endpoint: '',
|
||||
accessToken: null,
|
||||
|
||||
tokens: [],
|
||||
scopes: [],
|
||||
|
||||
form: {
|
||||
name: '',
|
||||
scopes: [],
|
||||
errors: []
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
validations: {
|
||||
form: {
|
||||
name: {
|
||||
required,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
//this.$refs.modalAccessToken.$refs.content.className = 'flex-auto';
|
||||
this.$refs.modalAccessToken.$refs.content.getElementsByClassName('sweet-content-content')[0].className = 'flex-auto';
|
||||
this.getTokens();
|
||||
this.getScopes();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all of the personal access tokens for the user.
|
||||
*/
|
||||
getTokens() {
|
||||
axios.get('oauth/personal-access-tokens')
|
||||
.then(response => {
|
||||
this.tokens = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all of the available scopes.
|
||||
*/
|
||||
getScopes() {
|
||||
axios.get('oauth/scopes')
|
||||
.then(response => {
|
||||
this.scopes = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Close all modals.
|
||||
*/
|
||||
closeModal() {
|
||||
this.$refs.form.reset();
|
||||
this.$v.$reset();
|
||||
this.$refs.modalCreateToken.close();
|
||||
this.$refs.modalAccessToken.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus on modal open.
|
||||
*/
|
||||
_focusInput() {
|
||||
const vm = this;
|
||||
setTimeout(function() {
|
||||
vm.$refs.createTokenName.focus();
|
||||
}, 10);
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the form for creating new tokens.
|
||||
*/
|
||||
showCreateTokenForm() {
|
||||
this.$refs.modalCreateToken.open();
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new personal access token.
|
||||
*/
|
||||
store() {
|
||||
this.$v.$touch();
|
||||
|
||||
if (this.$v.$invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.accessToken = null;
|
||||
|
||||
this.form.errors = [];
|
||||
|
||||
axios.post('oauth/personal-access-tokens', this.form)
|
||||
.then(response => {
|
||||
this.form.name = '';
|
||||
this.form.scopes = [];
|
||||
this.form.errors = [];
|
||||
|
||||
this.tokens.push(response.data.token);
|
||||
|
||||
this.showAccessToken(response.data.accessToken);
|
||||
})
|
||||
.catch(error => {
|
||||
if (typeof error.response.data === 'object') {
|
||||
this.form.errors = _.flatten(_.toArray(error.response.data));
|
||||
} else {
|
||||
this.form.errors = [this.$t('app.error_try_again')];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle the given scope in the list of assigned scopes.
|
||||
*/
|
||||
toggleScope(scope) {
|
||||
if (this.scopeIsAssigned(scope)) {
|
||||
this.form.scopes = _.reject(this.form.scopes, s => s === scope);
|
||||
} else {
|
||||
this.form.scopes.push(scope);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine if the given scope has been assigned to the token.
|
||||
*/
|
||||
scopeIsAssigned(scope) {
|
||||
return _.indexOf(this.form.scopes, scope) >= 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the given access token to the user.
|
||||
*/
|
||||
showAccessToken(accessToken) {
|
||||
this.$refs.modalCreateToken.close();
|
||||
|
||||
this.accessToken = accessToken;
|
||||
|
||||
this.$refs.modalAccessToken.open();
|
||||
},
|
||||
|
||||
/**
|
||||
* Revoke the given token.
|
||||
*/
|
||||
revoke(token) {
|
||||
axios.delete('oauth/personal-access-tokens/' + token.id)
|
||||
.then(response => {
|
||||
this.getTokens();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Copy text into clipboard
|
||||
*/
|
||||
copyIntoClipboard(text) {
|
||||
this.$copyText(text)
|
||||
.then(response => {
|
||||
this.notify(this.$t('settings.dav_clipboard_copied'), true);
|
||||
});
|
||||
},
|
||||
|
||||
notify(text, success) {
|
||||
this.$notify({
|
||||
group: 'passport-personal-access-token',
|
||||
title: text,
|
||||
text: '',
|
||||
type: success ? 'success' : 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
425
resources/js/components/people/Addresses.vue
Normal file
425
resources/js/components/people/Addresses.vue
Normal file
@@ -0,0 +1,425 @@
|
||||
<template>
|
||||
<div class="sidebar-box" :class="[ editMode ? 'edit' : '' ]">
|
||||
<div class="w-100 dt">
|
||||
<div class="sidebar-box-title">
|
||||
<h3>
|
||||
{{ $t('people.contact_address_title') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div v-if="contactAddresses.length > 0" class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<a v-if="!editMode" class="pointer" href="" @click.prevent="editMode = true">
|
||||
{{ $t('app.edit') }}
|
||||
</a>
|
||||
<a v-else class="pointer" href="" @click.prevent="toggleEditExcept(-1); resetState();">
|
||||
{{ $t('app.done') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EMPTY BOX - DISPLAY ADD BUTTON -->
|
||||
<p v-if="contactAddresses.length === 0 && !addMode" class="mb0">
|
||||
<a class="pointer" href="" @click.prevent="toggleAdd">
|
||||
{{ $t('app.add') }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<!-- LIST OF ADDRESSES -->
|
||||
<ul v-if="contactAddresses.length > 0">
|
||||
<li v-for="(contactAddress, i) in contactAddresses" :key="contactAddress.id" class="mb2">
|
||||
<div v-show="!contactAddress.edit" class="w-100 dt">
|
||||
<div class="dtc">
|
||||
<em class="f6 light-silver fa fa-globe pr2"></em>
|
||||
|
||||
<a v-if="!editMode" :href="contactAddress.googleMapAddress" target="_blank" rel="noopener noreferrer">
|
||||
{{ contactAddress.address }}
|
||||
</a>
|
||||
<span v-else>
|
||||
{{ contactAddress.address }}
|
||||
</span>
|
||||
|
||||
<span v-if="contactAddress.name" class="light-silver">
|
||||
({{ contactAddress.name }})
|
||||
</span>
|
||||
|
||||
<span v-if="!contactAddress.address">
|
||||
<a v-if="contactAddress.latitude" :href="contactAddress.googleMapAddressLatitude" target="_blank" rel="noopener noreferrer">
|
||||
({{ contactAddress.latitude }}, {{ contactAddress.longitude }})
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<div v-if="editMode" class="fr">
|
||||
<em class="fa fa-pencil-square-o pointer pr2" @click="toggleEdit(contactAddress)"></em>
|
||||
<em class="fa fa-trash-o pointer" @click="trash(contactAddress)"></em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EDIT BOX -->
|
||||
<div v-show="contactAddress.edit" class="w-100">
|
||||
<form class="measure center" @keyup.enter="update(contactAddress)">
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
:id="'name' + i"
|
||||
v-model="updateForm.name"
|
||||
:title="$t('people.contact_address_form_name')"
|
||||
input-type="text"
|
||||
:required="false"
|
||||
:iclass="'pa2 db w-100'"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
:id="'street' + i"
|
||||
v-model="updateForm.street"
|
||||
:title="$t('people.contact_address_form_street')"
|
||||
input-type="text"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
:id="'city' + i"
|
||||
v-model="updateForm.city"
|
||||
:title="$t('people.contact_address_form_city')"
|
||||
input-type="text"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
:id="'province' + i"
|
||||
v-model="updateForm.province"
|
||||
:title="$t('people.contact_address_form_province')"
|
||||
input-type="text"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
:id="'postal_code' + i"
|
||||
v-model="updateForm.postal_code"
|
||||
:title="$t('people.contact_address_form_postal_code')"
|
||||
input-type="text"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<form-select
|
||||
:id="'name' + i"
|
||||
v-model="updateForm.country"
|
||||
:title="$t('people.contact_address_form_country')"
|
||||
:options="countries"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
:id="'latitude' + i"
|
||||
v-model="updateForm.latitude"
|
||||
:title="$t('people.contact_address_form_latitude')"
|
||||
input-type="number"
|
||||
step="0.0000001"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
:id="'longitude' + i"
|
||||
v-model="updateForm.longitude"
|
||||
:title="$t('people.contact_address_form_longitude')"
|
||||
input-type="number"
|
||||
step="0.0000001"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="lh-copy mt3">
|
||||
<a class="btn btn-primary" href="" @click.prevent="update(contactAddress)">
|
||||
{{ $t('app.save') }}
|
||||
</a>
|
||||
<a class="btn" href="" @click.prevent="toggleEdit(contactAddress)">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- ADD BUTTON ONLY WHEN EDIT MODE IS AVAILABLE -->
|
||||
<li v-if="editMode && !addMode">
|
||||
<a class="pointer" href="" @click.prevent="toggleAdd">
|
||||
{{ $t('app.add') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<!-- ADD NEW ADDRESS -->
|
||||
<div v-if="addMode">
|
||||
<form class="measure center" @keyup.enter="store">
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
id="name"
|
||||
v-model="createForm.name"
|
||||
:title="$t('people.contact_address_form_name')"
|
||||
input-type="text"
|
||||
:required="false"
|
||||
:iclass="'pa2 db w-100'"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
id="street"
|
||||
v-model="createForm.street"
|
||||
:title="$t('people.contact_address_form_street')"
|
||||
input-type="text"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
id="city"
|
||||
v-model="createForm.city"
|
||||
:title="$t('people.contact_address_form_city')"
|
||||
input-type="text"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
id="province"
|
||||
v-model="createForm.province"
|
||||
:title="$t('people.contact_address_form_province')"
|
||||
input-type="text"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
id="postal_code"
|
||||
v-model="createForm.postal_code"
|
||||
:title="$t('people.contact_address_form_postal_code')"
|
||||
input-type="text"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<form-select
|
||||
id="name"
|
||||
v-model="createForm.country"
|
||||
:title="$t('people.contact_address_form_country')"
|
||||
:options="countries"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
id="latitude"
|
||||
v-model="createForm.latitude"
|
||||
:title="$t('people.contact_address_form_latitude')"
|
||||
input-type="number"
|
||||
step="0.0000001"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
id="longitude"
|
||||
v-model="createForm.longitude"
|
||||
:title="$t('people.contact_address_form_longitude')"
|
||||
input-type="number"
|
||||
step="0.0000001"
|
||||
:required="false"
|
||||
/>
|
||||
</div>
|
||||
<div class="lh-copy mt3">
|
||||
<a class="btn btn-primary" href="" @click.prevent="store">
|
||||
{{ $t('app.add') }}
|
||||
</a>
|
||||
<a class="btn" href="" @click.prevent="resetState">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
contactAddresses: [],
|
||||
countries: [],
|
||||
|
||||
editMode: false,
|
||||
addMode: false,
|
||||
|
||||
createForm: {
|
||||
name: '',
|
||||
street: '',
|
||||
city: '',
|
||||
province: '',
|
||||
postal_code: '',
|
||||
country: '',
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
},
|
||||
|
||||
updateForm: {
|
||||
id: '',
|
||||
name: '',
|
||||
street: '',
|
||||
city: '',
|
||||
province: '',
|
||||
postal_code: '',
|
||||
country: '',
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getAddresses();
|
||||
this.getCountries();
|
||||
},
|
||||
|
||||
getAddresses() {
|
||||
axios.get('people/' + this.hash + '/addresses')
|
||||
.then(response => {
|
||||
this.contactAddresses = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
getCountries() {
|
||||
axios.get('countries')
|
||||
.then(response => {
|
||||
this.countries = _.map(response.data, function(country) {
|
||||
return {
|
||||
id: country.id,
|
||||
name: country.country
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
reinitialize() {
|
||||
this.createForm.name = '';
|
||||
this.createForm.street = '';
|
||||
this.createForm.city = '';
|
||||
this.createForm.province = '';
|
||||
this.createForm.postal_code = '';
|
||||
this.createForm.country = '';
|
||||
this.createForm.latitude = '';
|
||||
this.createForm.longitude = '';
|
||||
},
|
||||
|
||||
resetState() {
|
||||
this.editMode = false;
|
||||
this.addMode = false;
|
||||
},
|
||||
|
||||
toggleAdd() {
|
||||
this.addMode = true;
|
||||
this.editMode = true;
|
||||
this.reinitialize();
|
||||
},
|
||||
|
||||
toggleEdit(contactAddress) {
|
||||
this.addMode = false;
|
||||
this.toggleEditExcept(contactAddress.id);
|
||||
Vue.set(contactAddress, 'edit', !contactAddress.edit);
|
||||
this.updateForm.id = contactAddress.id;
|
||||
this.updateForm.name = contactAddress.name;
|
||||
this.updateForm.street = contactAddress.street;
|
||||
this.updateForm.city = contactAddress.city;
|
||||
this.updateForm.province = contactAddress.province;
|
||||
this.updateForm.postal_code = contactAddress.postal_code;
|
||||
this.updateForm.country = contactAddress.country;
|
||||
this.updateForm.latitude = contactAddress.latitude;
|
||||
this.updateForm.longitude = contactAddress.longitude;
|
||||
},
|
||||
|
||||
toggleEditExcept(contactAddressId) {
|
||||
_.forEach(_.filter(this.contactAddresses, function (a) {
|
||||
return a.id !== contactAddressId;}
|
||||
), function (a) {
|
||||
Vue.set(a, 'edit', false);
|
||||
});
|
||||
},
|
||||
|
||||
store() {
|
||||
var vm = this;
|
||||
this.persistClient(
|
||||
'post', 'people/' + this.hash + '/addresses',
|
||||
this.createForm
|
||||
).then(response => {
|
||||
vm.contactAddresses.push(response.data);
|
||||
});
|
||||
|
||||
this.addMode = false;
|
||||
},
|
||||
|
||||
update(contactAddress) {
|
||||
var vm = this;
|
||||
Vue.set(contactAddress, 'edit', !contactAddress.edit);
|
||||
this.persistClient(
|
||||
'put', 'people/' + this.hash + '/addresses/' + contactAddress.id,
|
||||
this.updateForm
|
||||
).then(response => {
|
||||
Vue.set(vm.contactAddresses, vm.contactAddresses.indexOf(vm.contactAddresses.find(item => item.id === response.data.id)), response.data);
|
||||
});
|
||||
},
|
||||
|
||||
trash(contactAddress) {
|
||||
var vm = this;
|
||||
this.updateForm.id = contactAddress.id;
|
||||
|
||||
this.persistClient(
|
||||
'delete', 'people/' + this.hash + '/addresses/' + contactAddress.id,
|
||||
this.updateForm
|
||||
).then(response => {
|
||||
if (response.data.deleted === true) {
|
||||
vm.contactAddresses.splice(vm.contactAddresses.indexOf(vm.contactAddresses.find(item => item.id === response.data.id)), 1);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.contactAddresses.length <= 1) {
|
||||
this.editMode = false;
|
||||
}
|
||||
},
|
||||
|
||||
persistClient(method, uri, form) {
|
||||
form.errors = {};
|
||||
|
||||
return axios[method](uri, form)
|
||||
.catch(error => {
|
||||
if (typeof error.response.data === 'object') {
|
||||
form.errors = _.flatten(_.toArray(error.response.data));
|
||||
} else {
|
||||
form.errors = [this.$t('app.error_try_again')];
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
66
resources/js/components/people/Archive.vue
Normal file
66
resources/js/components/people/Archive.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<style scoped>
|
||||
.fa {
|
||||
top: 1px;
|
||||
color: #b1b1b1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<notifications group="archive" position="bottom right" :duration="5000" width="400" />
|
||||
|
||||
<a class="pointer" :title="$t('people.contact_archive_help')" href="" @click.prevent="toggle">
|
||||
{{ isActive ? $t('people.contact_archive') : $t('people.contact_unarchive') }}
|
||||
</a>
|
||||
|
||||
<span v-tooltip.top="$t('people.contact_archive_help')">
|
||||
<em class="fa fa-info-circle relative pointer"></em>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: false,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.isActive = this.active;
|
||||
},
|
||||
|
||||
toggle() {
|
||||
axios.put('people/' + this.hash + '/archive')
|
||||
.then(response => {
|
||||
this.isActive = response.data.is_active;
|
||||
|
||||
this.$notify({
|
||||
group: 'archive',
|
||||
title: this.$t('app.default_save_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
260
resources/js/components/people/ContactInformation.vue
Normal file
260
resources/js/components/people/ContactInformation.vue
Normal file
@@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<div class="sidebar-box" :class="[ editMode ? 'edit' : '' ]">
|
||||
<div class="w-100 dt">
|
||||
<div class="sidebar-box-title">
|
||||
<h3>
|
||||
{{ $t('people.contact_info_title') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div v-if="contactInformationData.length > 0" class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<a v-if="!editMode" class="pointer" href="" @click.prevent="editMode = true">
|
||||
{{ $t('app.edit') }}
|
||||
</a>
|
||||
<a v-else class="pointer" href="" @click.prevent="resetState">
|
||||
{{ $t('app.done') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="contactInformationData.length === 0 && !addMode" class="mb0">
|
||||
<a class="pointer" href="" @click.prevent="toggleAdd">
|
||||
{{ $t('app.add') }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<ul v-if="contactInformationData.length > 0">
|
||||
<li v-for="contactInformation in contactInformationData" :key="contactInformation.id" class="mb2">
|
||||
<div v-show="!contactInformation.edit" class="w-100 dt">
|
||||
<div class="dtc">
|
||||
<em v-if="contactInformation.fontawesome_icon" :class="contactInformation.fontawesome_icon" class="pr2 f6 light-silver"></em>
|
||||
<em v-else class="pr2 fa fa-address-card-o f6 gray"></em>
|
||||
|
||||
<a v-if="contactInformation.protocol" :href="contactInformation.protocol + contactInformation.data">
|
||||
{{ contactInformation.shortenName }}
|
||||
</a>
|
||||
<a v-else-if="contactInformation.data.indexOf('://') !== -1" :href="contactInformation.data">
|
||||
{{ contactInformation.shortenName }}
|
||||
</a>
|
||||
<span v-else>
|
||||
{{ contactInformation.shortenName }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="editMode" class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<em class="fa fa-pencil-square-o pointer pr2" @click="toggleEdit(contactInformation)"></em>
|
||||
<em class="fa fa-trash-o pointer" @click="trash(contactInformation)"></em>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="contactInformation.edit" class="w-100">
|
||||
<form class="measure center" @submit.prevent="update(contactInformation)">
|
||||
<div class="mt3">
|
||||
<form-input
|
||||
id="contact-content"
|
||||
v-model="updateForm.data"
|
||||
:title="$t('people.contact_info_form_content')"
|
||||
iclass="pa2 db w-100"
|
||||
:input-type="'text'"
|
||||
/>
|
||||
</div>
|
||||
<div class="lh-copy mt3">
|
||||
<a class="btn btn-primary" href="" @click.prevent="update(contactInformation)">
|
||||
{{ $t('app.save') }}
|
||||
</a>
|
||||
<a class="btn" href="" @click.prevent="toggleEdit(contactInformation)">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="editMode && !addMode">
|
||||
<a class="pointer" href="" @click.prevent="toggleAdd">
|
||||
{{ $t('app.add') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div v-if="addMode">
|
||||
<form class="measure center" @submit.prevent="store">
|
||||
<div class="mt3">
|
||||
<label for="add-contact-type" class="db fw6 lh-copy f6">
|
||||
{{ $t('people.contact_info_form_contact_type') }} <a class="fr normal" href="settings/personalization" target="_blank">
|
||||
{{ $t('people.contact_info_form_personalize') }}
|
||||
</a>
|
||||
</label>
|
||||
<select id="add-contact-type" v-model="createForm.contact_field_type_id" class="db w-100 h2">
|
||||
<option v-for="contactFieldType in contactFieldTypes" :key="contactFieldType.id" :value="contactFieldType.id">
|
||||
{{ contactFieldType.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<label class="db fw6 lh-copy f6">
|
||||
{{ $t('people.contact_info_form_content') }}
|
||||
</label>
|
||||
<input v-model="createForm.data" class="pa2 db w-100" type="text" />
|
||||
</div>
|
||||
<div class="lh-copy mt3">
|
||||
<a class="btn btn-primary" href="" @click.prevent="store">
|
||||
{{ $t('app.add') }}
|
||||
</a>
|
||||
<a class="btn" href="" @click.prevent="resetState">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
contactId: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
sizeLimit: {
|
||||
type: Number,
|
||||
default: 26,
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
contactInformationData: [],
|
||||
contactFieldTypes: [],
|
||||
|
||||
editMode: false,
|
||||
addMode: false,
|
||||
updateMode: false,
|
||||
|
||||
createForm: {
|
||||
contact_field_type_id: '',
|
||||
data: '',
|
||||
errors: []
|
||||
},
|
||||
|
||||
updateForm: {
|
||||
id: '',
|
||||
contact_field_type_id: '',
|
||||
data: '',
|
||||
edit: false,
|
||||
errors: []
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getContactInformationData();
|
||||
this.getContactFieldTypes();
|
||||
},
|
||||
|
||||
getContactInformationData() {
|
||||
axios.get('people/' + this.hash + '/contactfield')
|
||||
.then(response => {
|
||||
this.contactInformationData = this.formatResponse(response.data);
|
||||
});
|
||||
},
|
||||
|
||||
formatResponse(data) {
|
||||
var vm = this;
|
||||
_.each(data, function(value) {
|
||||
var shortenName = value.data;
|
||||
if (shortenName.length > vm.sizeLimit + 1) {
|
||||
shortenName = vm.$t('format.short_text', { text: shortenName.substr(0, vm.sizeLimit) });
|
||||
}
|
||||
value.shortenName = shortenName;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
getContactFieldTypes() {
|
||||
axios.get('people/' + this.hash + '/contactfieldtypes')
|
||||
.then(response => {
|
||||
this.contactFieldTypes = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
store() {
|
||||
this.persistClient(
|
||||
'post', 'people/' + this.hash + '/contactfield',
|
||||
this.createForm
|
||||
);
|
||||
|
||||
this.addMode = false;
|
||||
},
|
||||
|
||||
resetState() {
|
||||
this.editMode = false;
|
||||
this.addMode = false;
|
||||
},
|
||||
|
||||
toggleAdd() {
|
||||
this.addMode = true;
|
||||
this.editMode = true;
|
||||
this.createForm.data = '';
|
||||
this.createForm.contact_field_type_id = '';
|
||||
},
|
||||
|
||||
toggleEdit(contactField) {
|
||||
Vue.set(contactField, 'edit', !contactField.edit);
|
||||
this.updateForm.id = contactField.id;
|
||||
this.updateForm.data = contactField.data;
|
||||
this.updateForm.contact_field_type_id = contactField.contact_field_type_id;
|
||||
},
|
||||
|
||||
update(contactField) {
|
||||
this.persistClient(
|
||||
'put', 'people/' + this.hash + '/contactfield/' + contactField.id,
|
||||
this.updateForm
|
||||
);
|
||||
},
|
||||
|
||||
trash(contactField) {
|
||||
this.updateForm.id = contactField.id;
|
||||
|
||||
this.persistClient(
|
||||
'delete', 'people/' + this.hash + '/contactfield/' + contactField.id,
|
||||
this.updateForm
|
||||
);
|
||||
|
||||
if (this.contactInformationData.length <= 1) {
|
||||
this.editMode = false;
|
||||
}
|
||||
},
|
||||
|
||||
persistClient(method, uri, form) {
|
||||
form.errors = [];
|
||||
|
||||
axios[method](uri, form)
|
||||
.then(response => {
|
||||
this.getContactInformationData();
|
||||
})
|
||||
.catch(error => {
|
||||
if (typeof error.response.data === 'object') {
|
||||
form.errors = _.flatten(_.toArray(error.response.data));
|
||||
} else {
|
||||
form.errors = [this.$t('app.error_try_again')];
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
251
resources/js/components/people/ContactList.vue
Normal file
251
resources/js/components/people/ContactList.vue
Normal file
@@ -0,0 +1,251 @@
|
||||
<style>
|
||||
table.vgt-table img.avatar-search {
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
height: 43px;
|
||||
width: 43px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<vue-good-table
|
||||
mode="remote"
|
||||
:columns="columns"
|
||||
:rows="contacts"
|
||||
:total-rows="totalRecords"
|
||||
:row-style-class="getRowStyleClass"
|
||||
style-class="vgt-table"
|
||||
:rtl="!dirltr"
|
||||
:sort-options="{
|
||||
enabled: false,
|
||||
}"
|
||||
:pagination-options="{
|
||||
enabled: true,
|
||||
position: 'bottom',
|
||||
perPage: serverParams.perPage,
|
||||
page: 1,
|
||||
perPageDropdown: perPageDropdown,
|
||||
mode: 'pages',
|
||||
nextLabel: $t('people.people_search_next'),
|
||||
prevLabel: $t('people.people_search_prev'),
|
||||
rowsPerPageLabel: $t('people.people_search_rows_per_page'),
|
||||
ofLabel: $t('people.people_search_of'),
|
||||
pageLabel: $t('people.people_search_page'),
|
||||
allLabel: $t('people.people_search_all'),
|
||||
}"
|
||||
:search-options="{
|
||||
enabled: true,
|
||||
placeholder: $t('people.people_search'),
|
||||
}"
|
||||
@on-page-change="onPageChange"
|
||||
@on-per-page-change="onPerPageChange"
|
||||
@on-search="onSearch"
|
||||
@on-row-click="onRowClick"
|
||||
>
|
||||
<div slot="emptystate" class="tc">
|
||||
{{ $t('people.people_search_no_results') }}
|
||||
</div>
|
||||
<template #emptystate>
|
||||
<div v-if="!ready" class="vgt-center-align vgt-text-disabled h3">
|
||||
{{ $t('app.loading') }}
|
||||
</div>
|
||||
<div v-else class="vgt-center-align vgt-text-disabled h3">
|
||||
{{ $t('people.people_search_no_results') }}
|
||||
</div>
|
||||
</template>
|
||||
<template slot="table-row" slot-scope="props">
|
||||
<template v-if="props.column.field === 'avatar'">
|
||||
<contact-item
|
||||
:item="props.row"
|
||||
:with-name="false"
|
||||
:class-name="'avatar-search'"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="props.column.field === 'name'">
|
||||
<a :href="props.row.route">
|
||||
<template v-if="props.row.is_starred">
|
||||
<span :class="[dirltr === 'ltr' ? 'ml3' : 'mr3']">
|
||||
{{ props.row.complete_name }}
|
||||
</span>
|
||||
<svg class="relative" style="top: 5px" width="23" height="22" viewBox="0 0 23 22"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M14.6053 7.404L14.7224 7.68675L15.0275 7.7111C16.7206 7.84628 18.1486 7.92359 19.2895 7.98536C19.9026 8.01855 20.4327 8.04725 20.8765 8.07803C21.5288 8.12327 21.9886 8.17235 22.2913 8.24003C22.3371 8.25027 22.3765 8.26035 22.4102 8.27001C22.3896 8.29619 22.3651 8.32572 22.336 8.35877C22.1328 8.58945 21.7914 8.89714 21.2913 9.31475C20.9474 9.60184 20.5299 9.93955 20.0459 10.331C19.1625 11.0455 18.0577 11.9391 16.7762 13.0302L16.543 13.2288L16.614 13.5267C17.0045 15.1663 17.3689 16.5406 17.6601 17.6391C17.819 18.2381 17.956 18.7552 18.0638 19.1886C18.2209 19.8206 18.3149 20.2704 18.3428 20.5768C18.347 20.6222 18.3493 20.6616 18.3505 20.6957C18.3176 20.6838 18.2798 20.6688 18.2367 20.6502C17.9532 20.5277 17.5539 20.2981 17.0014 19.9522C16.6264 19.7175 16.1833 19.4306 15.671 19.099C14.7143 18.4797 13.5162 17.7042 12.0695 16.8199L11.8087 16.6604L11.5478 16.82C10.0753 17.7209 8.86032 18.5085 7.89223 19.136C7.3851 19.4648 6.94572 19.7496 6.57253 19.9838C6.01576 20.3332 5.61353 20.5656 5.32808 20.6899C5.28721 20.7077 5.25111 20.7222 5.21941 20.7339C5.22088 20.7009 5.22355 20.663 5.22783 20.6197C5.25839 20.3111 5.35605 19.8582 5.51781 19.2225C5.62627 18.7962 5.76269 18.2914 5.92018 17.7087C6.22053 16.5972 6.59748 15.2024 7.00309 13.5286L7.07553 13.2297L6.84141 13.0303C5.52399 11.9079 4.39683 10.9982 3.50024 10.2747C3.03915 9.90254 2.63904 9.57963 2.30539 9.30232C1.80195 8.88388 1.45729 8.57562 1.25116 8.34437C1.22315 8.31293 1.19929 8.28466 1.17903 8.25939C1.20999 8.25084 1.24557 8.24198 1.28628 8.233C1.58841 8.1663 2.048 8.11835 2.701 8.07418C3.1353 8.0448 3.65101 8.01744 4.24568 7.98589C5.39523 7.9249 6.83989 7.84824 8.56208 7.71111L8.86638 7.68688L8.98388 7.40514C9.61646 5.88824 10.1238 4.58366 10.5314 3.53571C10.7656 2.93365 10.9668 2.4163 11.1399 1.99205C11.3854 1.39027 11.5751 0.972355 11.7339 0.708729C11.7601 0.66516 11.7838 0.628777 11.8048 0.598565C11.8256 0.628571 11.849 0.664658 11.8748 0.707817C12.0327 0.971308 12.2212 1.38911 12.465 1.99089C12.6368 2.41509 12.8365 2.93242 13.0689 3.53445C13.4735 4.58244 13.9771 5.88709 14.6053 7.404Z"
|
||||
fill="#F2C94C" stroke="#DCBB58"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
<span v-else>
|
||||
{{ props.row.complete_name }}
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
<template v-else-if="props.column.field === 'description'">
|
||||
<a :href="props.row.route">
|
||||
<span v-if="props.row.description" :class="['i', dirltr === 'ltr' ? 'ml3' : 'mr3']">
|
||||
{{ props.row.description }}
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
</template>
|
||||
</vue-good-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ContactItem from './partials/ContactItem.vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
ContactItem,
|
||||
},
|
||||
|
||||
props: {
|
||||
showArchived: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
debounceWait: {
|
||||
type: Number,
|
||||
default: 200,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
contacts: [],
|
||||
searchEntries: null,
|
||||
ready: false,
|
||||
|
||||
totalRecords: 0,
|
||||
perPageDropdown: [30, 50, 100],
|
||||
|
||||
serverParams: {
|
||||
search: '',
|
||||
page: 1,
|
||||
perPage: 30
|
||||
},
|
||||
|
||||
columns: [
|
||||
{
|
||||
label: this.$t('app.contact_list_avatar'),
|
||||
field: 'avatar',
|
||||
width: '70px',
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
label: this.$t('app.contact_list_name'),
|
||||
field: 'name',
|
||||
},
|
||||
{
|
||||
label: this.$t('app.contact_list_description'),
|
||||
field: 'description',
|
||||
}
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.searchEntries = _.debounce(() => {
|
||||
this.ready = false;
|
||||
this.contacts = [];
|
||||
this._loadItems();
|
||||
}, this.debounceWait);
|
||||
|
||||
this._loadItems();
|
||||
},
|
||||
|
||||
methods: {
|
||||
onRowClick(params) {
|
||||
params.event.preventDefault();
|
||||
if (params.event.ctrlKey) {
|
||||
window.open(params.row.route, '_blank');
|
||||
return;
|
||||
}
|
||||
window.location.href = params.row.route;
|
||||
},
|
||||
|
||||
updateParams(newProps) {
|
||||
this.serverParams = Object.assign({}, this.serverParams, newProps);
|
||||
},
|
||||
|
||||
onPageChange(params) {
|
||||
this.updateParams({page: params.currentPage});
|
||||
this.searchEntries();
|
||||
},
|
||||
|
||||
onPerPageChange(params) {
|
||||
this.updateParams({perPage: params.currentPerPage, page: 1});
|
||||
this.searchEntries();
|
||||
},
|
||||
|
||||
onSearch(params) {
|
||||
this.updateParams({search: params.searchTerm});
|
||||
this.searchEntries();
|
||||
},
|
||||
|
||||
// load items is what brings back the rows from server
|
||||
_loadItems() {
|
||||
let urlParam = window.location.search;
|
||||
|
||||
if (urlParam) {
|
||||
urlParam += '&';
|
||||
} else {
|
||||
urlParam += '?';
|
||||
}
|
||||
|
||||
urlParam += 'page='+this.serverParams.page;
|
||||
urlParam += '&perPage='+this.serverParams.perPage;
|
||||
urlParam += '&search='+this.serverParams.search;
|
||||
if (this.showArchived) {
|
||||
urlParam += '&show_archived=true';
|
||||
}
|
||||
|
||||
this._loadNewItems(urlParam, (entries, total) => {
|
||||
this.contacts = entries;
|
||||
this.totalRecords = total;
|
||||
this.ready = true;
|
||||
});
|
||||
},
|
||||
|
||||
_loadNewItems(urlParam, after) {
|
||||
axios.get('people/list'+urlParam)
|
||||
.then(response => {
|
||||
if (_.isFunction(after)) {
|
||||
after(
|
||||
_.uniqBy(response.data.contacts, entry => _.uniqueId()),
|
||||
response.data.totalRecords
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getRowStyleClass() {
|
||||
return 'people-list-item bg-white pointer';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
106
resources/js/components/people/ContactMultiSearch.vue
Normal file
106
resources/js/components/people/ContactMultiSearch.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<style lang="scss" >
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<contact-autosuggest
|
||||
:id="id"
|
||||
ref="contactauto"
|
||||
:title="title"
|
||||
:required="required"
|
||||
:placeholder="placeholder"
|
||||
:component-item="componentItem"
|
||||
:filter="filter"
|
||||
:add-no-result="false"
|
||||
:input-class="'user-input-search-input'"
|
||||
:overflow="true"
|
||||
@select="select"
|
||||
/>
|
||||
<ul class="contacts mt2">
|
||||
<ul class="contacts-list table">
|
||||
<li v-for="contact in items" :key="contact.id" class="table-row">
|
||||
<div class="table-cell w-80">
|
||||
<strong>{{ contact.complete_name }}</strong>
|
||||
</div>
|
||||
<div class="table-cell actions">
|
||||
<a class="pointer" href="" @click.prevent="remove(contact)">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="contacts[]" :value="contact.id" />
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ContactAutosuggest from './partials/ContactAutosuggest.vue';
|
||||
import ContactMultiItem from './partials/ContactMultiItem.vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
ContactAutosuggest
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
placeholder : {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
userContactId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
contacts: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
items: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
componentItem() {
|
||||
return ContactMultiItem;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.items = this.contacts;
|
||||
},
|
||||
|
||||
methods: {
|
||||
filter(item) {
|
||||
return _.findIndex(this.items, i => { return i.id === item.id; }) < 0;
|
||||
},
|
||||
|
||||
select(contact) {
|
||||
if (contact.item && contact.item.id > 0 && this.filter(contact.item)) {
|
||||
this.items.push(contact.item);
|
||||
}
|
||||
},
|
||||
|
||||
remove(contact) {
|
||||
this.items.splice(this.items.indexOf(contact), 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
101
resources/js/components/people/ContactSearch.vue
Normal file
101
resources/js/components/people/ContactSearch.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<style lang="scss" >
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<contact-autosuggest
|
||||
:title="title"
|
||||
:required="required"
|
||||
:placeholder="placeholder"
|
||||
:component-item="componentItem"
|
||||
:input-class="'header-search-input'"
|
||||
@select="select"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ContactAutosuggest from './partials/ContactAutosuggest.vue';
|
||||
import ContactItem from './partials/ContactItem.vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
ContactAutosuggest
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
placeholder : {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
formNameOrder : {
|
||||
type: String,
|
||||
default: 'firstname',
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
componentItem() {
|
||||
return ContactItem;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
select(contact) {
|
||||
if (contact.item.id > 0) {
|
||||
window.location = contact.item.route;
|
||||
} else {
|
||||
// contact with ID = -1 is the 'add person' contact
|
||||
|
||||
const keyword = contact.item.keyword.trim();
|
||||
let names, email;
|
||||
|
||||
// attempt to extract name and email from 'first last <email@example.com>' format
|
||||
// https://stackoverflow.com/questions/9558608/regex-for-parsing-name-and-email-from-a-single-string
|
||||
const emailAndNameMatch = keyword.match(/(.*[^\s*<])?\s*<(.*)>/);
|
||||
|
||||
if(emailAndNameMatch === null) {
|
||||
names = keyword;
|
||||
} else {
|
||||
names = emailAndNameMatch[1];
|
||||
email = emailAndNameMatch[2];
|
||||
}
|
||||
|
||||
names = names.split(' ').map(name => _.capitalize(name));
|
||||
|
||||
let first_name, last_name;
|
||||
if (this.formNameOrder === 'firstname') {
|
||||
first_name = names[0];
|
||||
last_name = names.slice(1).join(' ');
|
||||
} else {
|
||||
first_name = names.slice(1).join(' ');
|
||||
last_name = names[0];
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (first_name) {
|
||||
params.set('first_name', first_name);
|
||||
}
|
||||
if (last_name) {
|
||||
params.set('last_name', last_name);
|
||||
}
|
||||
if (email) {
|
||||
params.set('email', email);
|
||||
}
|
||||
let p = params.toString();
|
||||
|
||||
window.location = 'people/add' + (p !== '' ? '?' + p : '');
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
146
resources/js/components/people/ContactSelect.vue
Normal file
146
resources/js/components/people/ContactSelect.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<div>
|
||||
<p v-if="title" class="mb2" :class="{ b: required }">
|
||||
{{ title }}
|
||||
</p>
|
||||
<input type="hidden" :name="name" :value="selected ? selected.id : ''" />
|
||||
<v-select
|
||||
:id="id ? id : ''"
|
||||
:value="selected"
|
||||
:placeholder="placeholder"
|
||||
:label="'complete_name'"
|
||||
:options="items"
|
||||
:dir="$root.htmldir"
|
||||
@search="search"
|
||||
@search:blur="blur"
|
||||
@search:focus="focus"
|
||||
@input="selected = $event; $emit('input', $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import vSelect from 'vue-select';
|
||||
import 'vue-select/dist/vue-select.css';
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
vSelect
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
userContactId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
defaultOptions: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
wait : {
|
||||
type: Number,
|
||||
default: 200,
|
||||
},
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
selected: null,
|
||||
items: [],
|
||||
callUpdateItems: null,
|
||||
cache: [],
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.selected = newValue;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.selected = this.value;
|
||||
this.callUpdateItems = _.debounce((text) => {
|
||||
this.getContacts(text, this)
|
||||
.then((response) => {
|
||||
this.cache[text] = response;
|
||||
this.displayItems(text);
|
||||
});
|
||||
}, this.wait);
|
||||
this.items = this.defaultOptions;
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
updateItems(text) {
|
||||
if (text === null) {
|
||||
return;
|
||||
}
|
||||
if (text.length < this.minLen) {
|
||||
this.items = [];
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.cache[text] === undefined) {
|
||||
this.callUpdateItems(text);
|
||||
} else {
|
||||
this.callUpdateItems.cancel();
|
||||
this.displayItems(text);
|
||||
}
|
||||
},
|
||||
|
||||
displayItems(text) {
|
||||
var datas = [];
|
||||
if (text === undefined || text.length === 0) {
|
||||
datas = this.defaultOptions;
|
||||
} else {
|
||||
datas = this.cache[text];
|
||||
}
|
||||
|
||||
datas = datas.filter(this.filter);
|
||||
|
||||
this.items = datas;
|
||||
},
|
||||
|
||||
getContacts(keyword, vm) {
|
||||
return axios.post('people/search', {
|
||||
needle: keyword
|
||||
}).then(function(response) {
|
||||
return response.data.data;
|
||||
});
|
||||
},
|
||||
|
||||
filter(item) {
|
||||
return this.userContactId !== item.id;
|
||||
},
|
||||
|
||||
search(keyword, loading) {
|
||||
this.updateItems(keyword);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
193
resources/js/components/people/Emotion.vue
Normal file
193
resources/js/components/people/Emotion.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<style scoped>
|
||||
.emotion-action-menu {
|
||||
border-radius: 3px;
|
||||
box-shadow: 1px 0px 1px rgba(43, 45, 80, 0.16), -1px 1px 1px rgba(43, 45, 80, 0.16), 0px 1px 4px rgba(43, 45, 80, 0.18);
|
||||
top: 34px;
|
||||
left: 0px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.emotion-action-menu li:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.emotion {
|
||||
background: #E5F3F9;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.emotion span {
|
||||
border-left-color: #A6C8D6;
|
||||
}
|
||||
|
||||
.emotion-add-arrow {
|
||||
right: 10px;
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
.emotion-list-line:hover {
|
||||
background-color: #f1f5fd;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="relative">
|
||||
<!-- CHOSEN EMOTIONS -->
|
||||
<ul v-show="chosenEmotions.length !== 0" class="mr2 di">
|
||||
<li v-for="chosenEmotion in chosenEmotions" :key="chosenEmotion.id" class="dib emotion br5 mr2">
|
||||
<span class="ph2 pv1 dib">
|
||||
{{ $t('app.emotion_' + chosenEmotion.name) }}
|
||||
</span>
|
||||
<span class="bl ph2 pv1 f6 pointer" @click.prevent="removeEmotion(chosenEmotion)">
|
||||
❌
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="relative dib">
|
||||
<a class="pointer small-btn pa2" @click.prevent="menu = true">
|
||||
😐 {{ $t('people.emotion_this_made_me_feel') }}
|
||||
</a>
|
||||
|
||||
<!-- MENU OF EMOTIONS -->
|
||||
<ul v-show="menu" class="absolute emotion-action-menu bg-white z-max pv1">
|
||||
<!-- PRIMARY -->
|
||||
<li v-for="primaryEmotion in primaryEmotions" v-show="emotionsMenu === 'primary'" :key="'primary' + primaryEmotion.id" class="pa2 pointer relative emotion-list-line" @click.prevent="showSecondary(primaryEmotion)">
|
||||
{{ $t('app.emotion_primary_' + primaryEmotion.name) }}
|
||||
|
||||
<svg class="absolute emotion-add-arrow" width="10" height="13" viewBox="0 0 10 13" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M8.75071 5.66783C9.34483 6.06361 9.34483 6.93653 8.75072 7.33231L1.80442 11.9598C1.13984 12.4025 0.25 11.9261 0.25 11.1275L0.25 1.87263C0.25 1.07409 1.13984 0.59767 1.80442 1.04039L8.75071 5.66783Z" fill="#C4C4C4" />
|
||||
</svg>
|
||||
</li>
|
||||
|
||||
<!-- SECONDARY -->
|
||||
<li v-show="emotionsMenu === 'secondary'" class="pa2 pointer bb b--gray-monica">
|
||||
<a class="no-underline" @click.prevent="emotionsMenu = 'primary'">
|
||||
← {{ $t('app.back') }}
|
||||
</a>
|
||||
</li>
|
||||
<li v-for="secondaryEmotion in secondaryEmotions" v-show="emotionsMenu === 'secondary'" :key="'secondary' + secondaryEmotion.id" class="pa2 pointer relative emotion-list-line" @click.prevent="showEmotion(secondaryEmotion)">
|
||||
{{ $t('app.emotion_secondary_' + secondaryEmotion.name) }}
|
||||
|
||||
<svg class="absolute emotion-add-arrow" width="10" height="13" viewBox="0 0 10 13" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M8.75071 5.66783C9.34483 6.06361 9.34483 6.93653 8.75072 7.33231L1.80442 11.9598C1.13984 12.4025 0.25 11.9261 0.25 11.1275L0.25 1.87263C0.25 1.07409 1.13984 0.59767 1.80442 1.04039L8.75071 5.66783Z" fill="#C4C4C4" />
|
||||
</svg>
|
||||
</li>
|
||||
|
||||
<!-- EMOTION -->
|
||||
<li v-show="emotionsMenu === 'emotions'" class="pa2 pointer bb b--gray-monica">
|
||||
<a class="no-underline" @click.prevent="emotionsMenu = 'secondary'">
|
||||
← {{ $t('app.back') }}
|
||||
</a>
|
||||
</li>
|
||||
<li v-for="emotion in emotions" v-show="emotionsMenu === 'emotions'" :key="emotion.id" class="pa2 pointer emotion-list-line" @click.prevent="addEmotion(emotion)">
|
||||
{{ $t('app.emotion_' + emotion.name) }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
initialEmotions: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
emotions: [],
|
||||
primaryEmotions: [],
|
||||
secondaryEmotions: [],
|
||||
selectedPrimaryEmotionId: 0,
|
||||
selectedSecondaryEmotionId: 0,
|
||||
chosenEmotions: [],
|
||||
menu: false,
|
||||
emotionsMenu: 'primary',
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
|
||||
this.chosenEmotions = this.initialEmotions;
|
||||
},
|
||||
|
||||
created() {
|
||||
window.addEventListener('click', this.close);
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('click', this.close);
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getPrimaryEmotions();
|
||||
},
|
||||
|
||||
close(e) {
|
||||
if (!this.$el.contains(e.target)) {
|
||||
this.menu = false;
|
||||
}
|
||||
},
|
||||
|
||||
getPrimaryEmotions() {
|
||||
axios.get('emotions')
|
||||
.then(response => {
|
||||
this.primaryEmotions = response.data.data;
|
||||
});
|
||||
},
|
||||
|
||||
getSecondaryEmotions() {
|
||||
axios.get('emotions/primaries/' + this.selectedPrimaryEmotionId + '/secondaries')
|
||||
.then(response => {
|
||||
this.secondaryEmotions = response.data.data;
|
||||
});
|
||||
},
|
||||
|
||||
getEmotions(id) {
|
||||
axios.get('emotions/primaries/' + this.selectedPrimaryEmotionId + '/secondaries/' + this.selectedSecondaryEmotionId + '/emotions')
|
||||
.then(response => {
|
||||
this.emotions = response.data.data;
|
||||
});
|
||||
},
|
||||
|
||||
showSecondary(primaryEmotion) {
|
||||
this.selectedPrimaryEmotionId = primaryEmotion.id;
|
||||
this.getSecondaryEmotions();
|
||||
this.emotionsMenu = 'secondary';
|
||||
},
|
||||
|
||||
showEmotion(secondaryEmotion) {
|
||||
this.selectedSecondaryEmotionId = secondaryEmotion.id;
|
||||
this.getEmotions();
|
||||
this.emotionsMenu = 'emotions';
|
||||
},
|
||||
|
||||
addEmotion(emotion) {
|
||||
this.menu = false;
|
||||
this.chosenEmotions.push(emotion);
|
||||
this.emotionsMenu = 'primary';
|
||||
this.$emit('update', this.chosenEmotions);
|
||||
},
|
||||
|
||||
removeEmotion(emotion) {
|
||||
this.chosenEmotions.splice(emotion, 1);
|
||||
this.$emit('update', this.chosenEmotions);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
138
resources/js/components/people/MeContact.vue
Normal file
138
resources/js/components/people/MeContact.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<style scoped style="scss">
|
||||
div >>> .avatar-small {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
font-size: 13px;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="!limited" class="flex fr">
|
||||
<a class="btn btn-primary" href="" @click.prevent="openModal">{{ $t('settings.me_select') }}</a>
|
||||
</div>
|
||||
<div v-if="meContact" class="dib pointer ml2 fl collapse">
|
||||
<span class="dt-row">
|
||||
<span class="dtc">
|
||||
<avatar :contact="meContact" :clickable="true" :imgclass="'avatar-small br1'" />
|
||||
</span>
|
||||
<span class="dtc">
|
||||
<a :href="'people/' + meContact.hash_id" class="avatar-small" :class="dirltr ? 'ml1' : 'mr1'">
|
||||
{{ meContact.complete_name }}
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else class="dib pointer fl">
|
||||
{{ $t('settings.me_no_contact') }}<br />
|
||||
<a v-if="!limited" href="" @click.prevent="openModal">{{ $t('settings.me_select_click') }}</a>
|
||||
<div v-else v-html="$t('settings.personalisation_paid_upgrade_vue', {url: 'settings/subscriptions' })"></div>
|
||||
</div>
|
||||
<div class="cb"></div>
|
||||
|
||||
<sweet-modal ref="modal" overlay-theme="dark" :title="$t('settings.me_select')">
|
||||
<form>
|
||||
<contact-select
|
||||
v-model="newContact"
|
||||
:required="true"
|
||||
:title="$t('settings.me_choose')"
|
||||
:name="'me_contact_id'"
|
||||
:placeholder="$t('settings.me_choose_placeholder')"
|
||||
:default-options="existingContacts"
|
||||
/>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn fl" href="" @click.prevent="remove">
|
||||
{{ $t('settings.me_remove_contact') }}
|
||||
</a>
|
||||
<a class="btn" href="" @click.prevent="closeModal">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a class="btn btn-primary" href="" @click.prevent="save">
|
||||
{{ $t('app.save') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SweetModal,
|
||||
},
|
||||
|
||||
props: {
|
||||
contact: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
existingContacts: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
limited: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
meContact: null,
|
||||
newContact: null
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
contact(value) {
|
||||
this.newContact = value;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.meContact = this.contact;
|
||||
this.newContact = this.contact;
|
||||
},
|
||||
|
||||
methods: {
|
||||
save() {
|
||||
axios.post('me/contact', {
|
||||
contact_id: this.newContact.id
|
||||
})
|
||||
.then(response => {
|
||||
this.$emit('change', this.newContact);
|
||||
this.meContact = this.newContact;
|
||||
this.closeModal();
|
||||
});
|
||||
},
|
||||
|
||||
remove() {
|
||||
this.newContact = null;
|
||||
axios.delete('me/contact')
|
||||
.then(response => {
|
||||
this.$emit('change', null);
|
||||
this.meContact = null;
|
||||
this.closeModal();
|
||||
});
|
||||
},
|
||||
|
||||
openModal() {
|
||||
this.$refs.modal.open();
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
this.$refs.modal.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
229
resources/js/components/people/Notes.vue
Normal file
229
resources/js/components/people/Notes.vue
Normal file
@@ -0,0 +1,229 @@
|
||||
<style scoped>
|
||||
.note:hover {
|
||||
background-color: #f6fbff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<notifications group="main" position="bottom right" />
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<form class="bg-near-white pa2 br2 mb3">
|
||||
<textarea v-model="newNote.body" v-cy-name="'add-note-textarea'" class="w-100 br2 pa2 b--light-gray" :placeholder="$t('people.notes_add_cta')" @focus="addMode = true"
|
||||
@keyup.esc="addMode = false"
|
||||
></textarea>
|
||||
<a v-if="addMode" v-cy-name="'add-note-button'" class="pointer btn btn-primary" href="" @click.prevent="store">
|
||||
{{ $t('app.add') }}
|
||||
</a>
|
||||
<a v-if="addMode" v-cy-name="'cancel-note-button'" class="pointer btn btn-secondary" href="" @click.prevent="addMode = false">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- LIST OF NORMAL NOTES -->
|
||||
<ul v-cy-name="'notes-body'" v-cy-items="notes.map(n => n.id)">
|
||||
<li v-for="note in notes" :key="note.id" class="note">
|
||||
<div v-show="!note.edit" v-cy-name="'note-body-' + note.id" class="ba br2 b--black-10 br--top w-100 mb2">
|
||||
<div class="pa2 markdown">
|
||||
<span dir="auto" v-html="compiledMarkdown(note.body)"></span>
|
||||
</div>
|
||||
<div class="pa2 cf bt b--black-10 br--bottom f7 lh-copy">
|
||||
<div class="fl w-50">
|
||||
<div class="f5 di mr1">
|
||||
<em v-tooltip.top="$t('people.notes_favorite')" class="pointer" :class="[note.is_favorited ? 'fa fa-star' : 'fa fa-star-o']" @click="toggleFavorite(note)"></em>
|
||||
</div>
|
||||
{{ note.created_at_short }}
|
||||
</div>
|
||||
<div class="fl w-50 tr">
|
||||
<a v-cy-name="'edit-note-button-' + note.id" class="pointer" href="" @click.prevent="toggleEditMode(note)">
|
||||
{{ $t('app.edit') }}
|
||||
</a>
|
||||
|
|
||||
<a v-cy-name="'delete-note-button-' + note.id" class="pointer" href="" @click.prevent="showDelete(note)">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EDIT MODE -->
|
||||
<form v-show="note.edit" class="bg-near-white pa2 br2 mt3 mb3">
|
||||
<textarea v-model="note.body" v-cy-name="'edit-note-body-' + note.id" class="w-100 br2 pa2 b--light-gray" @keyup.esc="note.edit = false"></textarea>
|
||||
<a v-cy-name="'edit-mode-note-button-' + note.id" class="pointer btn btn-primary" href="" @click.prevent="update(note)">
|
||||
{{ $t('app.update') }}
|
||||
</a>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Delete Note modal -->
|
||||
<sweet-modal ref="modalDeleteNote" v-cy-name="'modal-delete-note'"
|
||||
overlay-theme="dark"
|
||||
:title="$t('people.notes_delete_title')"
|
||||
>
|
||||
<p>
|
||||
{{ $t('people.notes_delete_confirmation') }}
|
||||
</p>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeModal">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a v-cy-name="'delete-mode-note-button-' + deleteNote.id" class="btn btn-primary" href=""
|
||||
@click.prevent="trash(deleteNote)"
|
||||
>
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
SweetModal,
|
||||
},
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
notes: [],
|
||||
|
||||
addMode: false,
|
||||
editMode: false,
|
||||
|
||||
newNote: {
|
||||
id: 0,
|
||||
body: '',
|
||||
is_favorited: 0
|
||||
},
|
||||
|
||||
deleteNote: {
|
||||
id: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getNotes();
|
||||
},
|
||||
|
||||
reinitialize() {
|
||||
this.createForm.body = '';
|
||||
},
|
||||
|
||||
favorited: function (notes) {
|
||||
return notes.filter(function (note) {
|
||||
return note.is_favorited === true;
|
||||
});
|
||||
},
|
||||
|
||||
normal: function (notes) {
|
||||
return notes.filter(function (note) {
|
||||
return note.is_favorited === false;
|
||||
});
|
||||
},
|
||||
|
||||
toggleEditMode(note) {
|
||||
Vue.set(note, 'edit', !note.edit);
|
||||
},
|
||||
|
||||
getNotes() {
|
||||
axios.get('people/' + this.hash + '/notes')
|
||||
.then(response => {
|
||||
this.notes = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
store() {
|
||||
axios.post('people/' + this.hash + '/notes', this.newNote)
|
||||
.then(response => {
|
||||
this.newNote.body = '';
|
||||
this.getNotes();
|
||||
this.addMode = false;
|
||||
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('people.notes_create_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
toggleFavorite(note) {
|
||||
axios.post('people/' + this.hash + '/notes/' + note.id + '/toggle')
|
||||
.then(response => {
|
||||
this.getNotes();
|
||||
});
|
||||
},
|
||||
|
||||
update(note) {
|
||||
axios.put('people/' + this.hash + '/notes/' + note.id, note)
|
||||
.then(response => {
|
||||
Vue.set(note, 'edit', false);
|
||||
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('people.notes_update_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
showDelete(note) {
|
||||
this.deleteNote.id = note.id;
|
||||
this.$refs.modalDeleteNote.open();
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
this.$refs.modalDeleteNote.close();
|
||||
},
|
||||
|
||||
trash(note) {
|
||||
axios.delete('people/' + this.hash + '/notes/' + note.id)
|
||||
.then(response => {
|
||||
this.getNotes();
|
||||
|
||||
this.closeModal();
|
||||
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('people.notes_delete_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
compiledMarkdown (text) {
|
||||
return text !== undefined && text !== null ? marked(text, { sanitize: true }) : '';
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
128
resources/js/components/people/Participant.vue
Normal file
128
resources/js/components/people/Participant.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<style scoped>
|
||||
.participant {
|
||||
background: #E5F3F9;
|
||||
border-radius: 7px;
|
||||
}
|
||||
.participant-list {
|
||||
height: 150px;
|
||||
}
|
||||
input[type=text] {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
input[type=text]:focus {
|
||||
background-color: #fff;
|
||||
}
|
||||
.potential-participant:hover {
|
||||
background-color: #f1f5fd;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<ul v-show="chosenParticipants.length !== 0" class="mr2 mb3">
|
||||
<li v-for="chosenParticipant in chosenParticipants"
|
||||
:key="chosenParticipant.id"
|
||||
class="dib participant br5 mr2"
|
||||
>
|
||||
<span class="ph2 pv1 dib">
|
||||
{{ chosenParticipant.name }}
|
||||
</span>
|
||||
<span class="bl ph2 pv1 f6 pointer" @click.prevent="remove(chosenParticipant)">
|
||||
❌
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-show="participants.length !== 0" class="ba b--gray-monica">
|
||||
<span class="db bb b--gray-monica pa2">
|
||||
<input v-model="search" type="text" :placeholder="$t('app.filter')" class="br2 f5 w-100 ba b--black-20 pa2 outline-0" />
|
||||
</span>
|
||||
<ul class="overflow-auto participant-list">
|
||||
<li v-for="fparticipant in filteredList"
|
||||
:key="fparticipant.id"
|
||||
class="bb b--gray-monica pa2 pointer potential-participant"
|
||||
@click.prevent="select(fparticipant)"
|
||||
>
|
||||
{{ fparticipant.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
|
||||
props: {
|
||||
initialParticipants: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
search: '',
|
||||
participants: [],
|
||||
chosenParticipants: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
filteredList() {
|
||||
// filter the list when searching
|
||||
// also, sort the list by name
|
||||
var list;
|
||||
list = this.participants.filter(participant => {
|
||||
return participant.name.toLowerCase().includes(this.search.toLowerCase())
|
||||
&& this.chosenParticipants.find(p => p.id === participant.id) === undefined;
|
||||
});
|
||||
|
||||
function compare(a, b) {
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return list.sort(compare);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
this.chosenParticipants = this.initialParticipants;
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getParticipants();
|
||||
},
|
||||
|
||||
getParticipants: function () {
|
||||
axios.get('people/' + this.hash + '/activities/contacts')
|
||||
.then(response => {
|
||||
this.participants = _.toArray(response.data);
|
||||
});
|
||||
},
|
||||
|
||||
select(participant) {
|
||||
this.chosenParticipants.push(participant);
|
||||
this.participants.splice(this.participants.indexOf(participant), 1);
|
||||
this.$emit('update', this.chosenParticipants);
|
||||
},
|
||||
|
||||
remove(participant) {
|
||||
this.participants.push(participant);
|
||||
this.chosenParticipants.splice(this.chosenParticipants.indexOf(participant), 1);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
249
resources/js/components/people/Pets.vue
Normal file
249
resources/js/components/people/Pets.vue
Normal file
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<div class="sidebar-box" :class="[ editMode ? 'edit' : '' ]">
|
||||
<notifications group="main" position="bottom right" />
|
||||
|
||||
<div class="w-100 dt">
|
||||
<div class="sidebar-box-title">
|
||||
<h3>{{ $t('people.pets_title') }}</h3>
|
||||
</div>
|
||||
<div v-if="pets.length > 0" class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<a v-if="!editMode" class="pointer" href="" @click.prevent="editMode = true">
|
||||
{{ $t('app.edit') }}
|
||||
</a>
|
||||
<a v-else class="pointer" href="" @click.prevent="resetState">
|
||||
{{ $t('app.done') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add button when box is empty -->
|
||||
<p v-if="pets.length === 0 && !addMode" class="mb0">
|
||||
<a class="pointer" href="" @click.prevent="toggleAdd">
|
||||
{{ $t('app.add') }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<!-- List of pets -->
|
||||
<ul v-if="pets.length > 0">
|
||||
<li v-for="(pet, i) in pets" :key="pet.id" class="mb2">
|
||||
<div v-show="!pet.edit" class="w-100 dt">
|
||||
<div class="dtc">
|
||||
{{ $t('people.pets_' + pet.category_name) }}
|
||||
<span v-if="pet.name">
|
||||
- {{ pet.name }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="editMode" class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<em class="fa fa-pencil-square-o pointer pr2" @click="toggleEdit(pet)"></em>
|
||||
<em class="fa fa-trash-o pointer" @click="trash(pet)"></em>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pet edit form -->
|
||||
<div v-show="pet.edit" class="w-100">
|
||||
<form class="measure center">
|
||||
<div class="mt3">
|
||||
<label :for="'edit-category' + i" class="db fw6 lh-copy f6">
|
||||
{{ $t('people.pets_kind') }}
|
||||
</label>
|
||||
<select :id="'edit-category' + i" v-model="updateForm.pet_category_id" class="db w-100 h2">
|
||||
<option v-for="petCategory in petCategories" :key="petCategory.id" :value="petCategory.id">
|
||||
{{ $t('people.pets_' + petCategory.name) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<label :for="'edit-name' + i" class="db fw6 lh-copy f6">
|
||||
{{ $t('people.pets_name') }}
|
||||
</label>
|
||||
<input :id="'edit-name' + i" v-model="updateForm.name" class="pa2 db w-100" type="text" @keyup.enter="update(pet)" />
|
||||
</div>
|
||||
<div class="lh-copy mt3">
|
||||
<a class="btn btn-primary" href="" @click.prevent="update(pet)">
|
||||
{{ $t('app.save') }}
|
||||
</a>
|
||||
<a class="btn" href="" @click.prevent="toggleEdit(pet)">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="editMode && !addMode">
|
||||
<a class="pointer" href="" @click.prevent="toggleAdd">
|
||||
{{ $t('app.add') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Pet Add form -->
|
||||
<div v-if="addMode">
|
||||
<form class="measure center">
|
||||
<div class="mt3">
|
||||
<label for="add-category" class="db fw6 lh-copy f6">
|
||||
{{ $t('people.pets_kind') }}
|
||||
</label>
|
||||
<select id="add-category" v-model="createForm.pet_category_id" class="db w-100 h2">
|
||||
<option v-for="petCategory in petCategories" :key="petCategory.id" :value="petCategory.id">
|
||||
{{ $t('people.pets_' + petCategory.name) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<label for="add-name" class="db fw6 lh-copy f6">
|
||||
{{ $t('people.pets_name') }}
|
||||
</label>
|
||||
<input id="add-name" v-model="createForm.name" class="pa2 db w-100" type="text" @keyup.enter="store"
|
||||
@keyup.esc="resetState"
|
||||
/>
|
||||
</div>
|
||||
<div class="lh-copy mt3">
|
||||
<a class="btn btn-primary" href="" @click.prevent="store">
|
||||
{{ $t('app.add') }}
|
||||
</a>
|
||||
<a class="btn" href="" @click.prevent="resetState">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
petCategories: [],
|
||||
pets: [],
|
||||
|
||||
editMode: false,
|
||||
addMode: false,
|
||||
updateMode: false,
|
||||
|
||||
createForm: {
|
||||
pet_category_id: '',
|
||||
name: '',
|
||||
errors: []
|
||||
},
|
||||
|
||||
updateForm: {
|
||||
id: '',
|
||||
pet_category_id: '',
|
||||
name: '',
|
||||
edit: false,
|
||||
errors: []
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getPetCategories();
|
||||
this.getPets();
|
||||
},
|
||||
|
||||
getPetCategories() {
|
||||
axios.get('petcategories')
|
||||
.then(response => {
|
||||
this.petCategories = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
getPets() {
|
||||
axios.get('people/' + this.hash + '/pets')
|
||||
.then(response => {
|
||||
this.pets = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
store() {
|
||||
axios.post('people/' + this.hash + '/pets', this.createForm)
|
||||
.then(response => {
|
||||
this.addMode = false;
|
||||
this.pets.push(response.data);
|
||||
this.createForm.name = '';
|
||||
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('people.pets_create_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
resetState() {
|
||||
this.editMode = false;
|
||||
this.addMode = false;
|
||||
},
|
||||
|
||||
toggleAdd() {
|
||||
this.addMode = true;
|
||||
this.editMode = true;
|
||||
this.createForm.data = '';
|
||||
this.createForm.pet_category_id = '';
|
||||
},
|
||||
|
||||
toggleEdit(pet) {
|
||||
Vue.set(pet, 'edit', !pet.edit);
|
||||
this.updateForm.id = pet.id;
|
||||
this.updateForm.name = pet.name;
|
||||
this.updateForm.pet_category_id = pet.pet_category_id;
|
||||
},
|
||||
|
||||
update(pet) {
|
||||
axios.put('people/' + this.hash + '/pets/' + pet.id, this.updateForm)
|
||||
.then(response => {
|
||||
Vue.set(pet, 'edit', !pet.edit);
|
||||
Vue.set(pet, 'name', response.data.name);
|
||||
Vue.set(pet, 'pet_category_id', response.data.pet_category_id);
|
||||
Vue.set(pet, 'category_name', response.data.category_name);
|
||||
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('people.pets_update_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
trash(pet) {
|
||||
axios.delete('people/' + this.hash + '/pets/' + pet.id)
|
||||
.then(response => {
|
||||
this.getPets();
|
||||
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('people.pets_delete_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
|
||||
if (this.pets.length <= 1) {
|
||||
this.resetState();
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
219
resources/js/components/people/SetAvatar.vue
Normal file
219
resources/js/components/people/SetAvatar.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<div class="pa4-ns ph3 pv2 bb b--gray-monica">
|
||||
<p>{{ $t('people.avatar_question') }}</p>
|
||||
<div class="mb3 mb0-ns">
|
||||
<!-- Default avatar -->
|
||||
<form-radio
|
||||
v-model="selectedAvatar"
|
||||
:name="'avatar'"
|
||||
:value="'default'"
|
||||
:dclass="'flex mb1'"
|
||||
:iclass="dirltr ? 'mr2' : 'ml2'"
|
||||
>
|
||||
<template slot="label">
|
||||
{{ $t('people.avatar_default_avatar') }}
|
||||
</template>
|
||||
<div slot="extra">
|
||||
<img class="mb4 pa2 ba b--gray-monica br3" style="width: 150px" :src="defaultUrl" alt="" />
|
||||
</div>
|
||||
</form-radio>
|
||||
|
||||
<!-- Adorable avatar -->
|
||||
<form-radio
|
||||
v-model="selectedAvatar"
|
||||
:name="'avatar'"
|
||||
:value="'adorable'"
|
||||
:dclass="'flex mb1'"
|
||||
:iclass="dirltr ? 'mr2' : 'ml2'"
|
||||
>
|
||||
<template slot="label">
|
||||
{{ $t('people.avatar_adorable_avatar') }}
|
||||
</template>
|
||||
<div slot="extra">
|
||||
<img class="mb4 pa2 ba b--gray-monica br3" style="width: 150px" :src="adorableUrl" alt="" />
|
||||
</div>
|
||||
</form-radio>
|
||||
|
||||
<!-- Gravatar -->
|
||||
<form-radio
|
||||
v-if="gravatarUrl"
|
||||
v-model="selectedAvatar"
|
||||
:name="'avatar'"
|
||||
:value="'gravatar'"
|
||||
:dclass="'flex mb1'"
|
||||
:iclass="dirltr ? 'mr2' : 'ml2'"
|
||||
>
|
||||
<template slot="label">
|
||||
<span v-html="$t('people.avatar_gravatar')"></span>
|
||||
</template>
|
||||
<div slot="extra">
|
||||
<img class="mb4 pa2 ba b--gray-monica br3" style="width: 150px" :src="gravatarUrl" alt="" />
|
||||
</div>
|
||||
</form-radio>
|
||||
|
||||
<!-- Existing avatar -->
|
||||
<form-radio
|
||||
v-if="initialAvatar === 'photo'"
|
||||
v-model="selectedAvatar"
|
||||
:name="'avatar'"
|
||||
:value="'photo'"
|
||||
:dclass="'flex mb1'"
|
||||
:iclass="dirltr ? 'mr2' : 'ml2'"
|
||||
>
|
||||
<template slot="label">
|
||||
{{ $t('people.avatar_current') }}
|
||||
</template>
|
||||
<div slot="extra">
|
||||
<img class="mb4 pa2 ba b--gray-monica br3" style="width: 150px" :src="photoUrl" alt="" />
|
||||
</div>
|
||||
</form-radio>
|
||||
|
||||
<!-- Upload avatar -->
|
||||
<form-radio
|
||||
v-model="selectedAvatar"
|
||||
:name="'avatar'"
|
||||
:value="'upload'"
|
||||
:dclass="'flex mb1'"
|
||||
:iclass="dirltr ? 'mr2' : 'ml2'"
|
||||
:disabled="hasReachedAccountStorageLimit"
|
||||
>
|
||||
<template slot="label">
|
||||
{{ $t('people.avatar_photo') }}
|
||||
<span v-if="hasReachedAccountStorageLimit">
|
||||
<a href="settings/subscriptions">
|
||||
{{ $t('app.upgrade') }}
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
<div slot="extra">
|
||||
<input ref="uploadedImg"
|
||||
type="file"
|
||||
class="form-control-file"
|
||||
name="photo"
|
||||
:disabled="hasReachedAccountStorageLimit"
|
||||
@change="uploadImg($event)"
|
||||
/>
|
||||
<small class="form-text text-muted">
|
||||
{{ $t('people.information_edit_max_size2', { size: maxUploadSize }) }}
|
||||
</small>
|
||||
<img v-if="croppedImgUrl" class="mb4 pa2 ba b--gray-monica br3" style="width: 150px" :src="croppedImgUrl" alt="" />
|
||||
</div>
|
||||
</form-radio>
|
||||
</div>
|
||||
<sweet-modal ref="cropModal" :title="$t('people.avatar_crop_new_avatar_photo')" :blocking="true" :hide-close-button="true">
|
||||
<clipper-basic ref="clipper" :src="uploadedImgUrl" :ratio="1" :init-width="100" :init-height="100" />
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="cancelCrop">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a class="btn btn-primary" href="" @click.prevent="setCroppedImg">
|
||||
{{ $t('app.done') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { clipperBasic } from 'vuejs-clipper';
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
clipperBasic,
|
||||
SweetModal
|
||||
},
|
||||
props: {
|
||||
avatar: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
defaultUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
adorableUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
gravatarUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
photoUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
hasReachedAccountStorageLimit: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxUploadSize: {
|
||||
type: Number,
|
||||
default: 10000,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedAvatar: '',
|
||||
initialAvatar: '',
|
||||
uploadedImgUrl: '',
|
||||
croppedImgUrl: '',
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
avatar(val) {
|
||||
this.selectedAvatar = val;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.initialAvatar = this.avatar;
|
||||
this.selectedAvatar = this.avatar;
|
||||
},
|
||||
|
||||
methods: {
|
||||
uploadImg: function(e) {
|
||||
if (e.target.files.length !== 0) {
|
||||
if(this.uploadedImgUrl) {
|
||||
URL.revokeObjectURL(this.uploadedImgUrl);
|
||||
}
|
||||
this.uploadedImgUrl = window.URL.createObjectURL(e.target.files[0]);
|
||||
this.$refs.cropModal.open();
|
||||
}
|
||||
},
|
||||
|
||||
setCroppedImg: function () {
|
||||
const canvas = this.$refs.clipper.clip();
|
||||
|
||||
canvas.toBlob((blob) => {
|
||||
const input = this.$refs.uploadedImg;
|
||||
const file = new File([blob], input.files[0].name, { type: 'image/jpeg' });
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(file);
|
||||
input.files = dataTransfer.files;
|
||||
|
||||
this.croppedImgUrl = window.URL.createObjectURL(blob);
|
||||
}, 'image/jpeg', 1);
|
||||
|
||||
this.$refs.cropModal.close();
|
||||
},
|
||||
|
||||
cancelCrop() {
|
||||
const dataTransfer = new DataTransfer();
|
||||
this.$refs.uploadedImg.files = dataTransfer.files;
|
||||
this.croppedImgUrl = '';
|
||||
this.$refs.cropModal.close();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
63
resources/js/components/people/SetFavorite.vue
Normal file
63
resources/js/components/people/SetFavorite.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="di f5 normal">
|
||||
<notifications group="favorite" position="top middle" width="400" />
|
||||
|
||||
<svg v-if="!isFavorite" v-tooltip.top="$t('people.set_favorite')" v-cy-name="'set-favorite'" class="pointer" width="23"
|
||||
height="22" viewBox="0 0 23 22" fill="none" xmlns="http://www.w3.org/2000/svg" @click="store(true)"
|
||||
>
|
||||
<path d="M14.6053 7.404L14.7224 7.68675L15.0275 7.7111C16.7206 7.84628 18.1486 7.92359 19.2895 7.98536C19.9026 8.01855 20.4327 8.04725 20.8765 8.07803C21.5288 8.12327 21.9886 8.17235 22.2913 8.24003C22.3371 8.25027 22.3765 8.26035 22.4102 8.27001C22.3896 8.29619 22.3651 8.32572 22.336 8.35877C22.1328 8.58945 21.7914 8.89714 21.2913 9.31475C20.9474 9.60184 20.5299 9.93955 20.0459 10.331C19.1625 11.0455 18.0577 11.9391 16.7762 13.0302L16.543 13.2288L16.614 13.5267C17.0045 15.1663 17.3689 16.5406 17.6601 17.6391C17.819 18.2381 17.956 18.7552 18.0638 19.1886C18.2209 19.8206 18.3149 20.2704 18.3428 20.5768C18.347 20.6222 18.3493 20.6616 18.3505 20.6957C18.3176 20.6838 18.2798 20.6688 18.2367 20.6502C17.9532 20.5277 17.5539 20.2981 17.0014 19.9522C16.6264 19.7175 16.1833 19.4306 15.671 19.099C14.7143 18.4797 13.5162 17.7042 12.0695 16.8199L11.8087 16.6604L11.5478 16.82C10.0753 17.7209 8.86032 18.5085 7.89223 19.136C7.3851 19.4648 6.94572 19.7496 6.57253 19.9838C6.01576 20.3332 5.61353 20.5656 5.32808 20.6899C5.28721 20.7077 5.25111 20.7222 5.21941 20.7339C5.22088 20.7009 5.22355 20.663 5.22783 20.6197C5.25839 20.3111 5.35605 19.8582 5.51781 19.2225C5.62627 18.7962 5.76269 18.2914 5.92018 17.7087C6.22053 16.5972 6.59748 15.2024 7.00309 13.5286L7.07553 13.2297L6.84141 13.0303C5.52399 11.9079 4.39683 10.9982 3.50024 10.2747C3.03915 9.90254 2.63904 9.57963 2.30539 9.30232C1.80195 8.88388 1.45729 8.57562 1.25116 8.34437C1.22315 8.31293 1.19929 8.28466 1.17903 8.25939C1.20999 8.25084 1.24557 8.24198 1.28628 8.233C1.58841 8.1663 2.048 8.11835 2.701 8.07418C3.1353 8.0448 3.65101 8.01744 4.24568 7.98589C5.39523 7.9249 6.83989 7.84824 8.56208 7.71111L8.86638 7.68688L8.98388 7.40514C9.61646 5.88824 10.1238 4.58366 10.5314 3.53571C10.7656 2.93365 10.9668 2.4163 11.1399 1.99205C11.3854 1.39027 11.5751 0.972355 11.7339 0.708729C11.7601 0.66516 11.7838 0.628777 11.8048 0.598565C11.8256 0.628571 11.849 0.664658 11.8748 0.707817C12.0327 0.971308 12.2212 1.38911 12.465 1.99089C12.6368 2.41509 12.8365 2.93242 13.0689 3.53445C13.4735 4.58244 13.9771 5.88709 14.6053 7.404Z" fill="#D9D8D8" fill-opacity="0.25" stroke="#C6C6C6" />
|
||||
</svg>
|
||||
|
||||
<svg v-else v-cy-name="'unset-favorite'" class="pointer" width="23" height="22"
|
||||
viewBox="0 0 23 22" fill="none" xmlns="http://www.w3.org/2000/svg" @click="store(false)"
|
||||
>
|
||||
<path d="M14.6053 7.404L14.7224 7.68675L15.0275 7.7111C16.7206 7.84628 18.1486 7.92359 19.2895 7.98536C19.9026 8.01855 20.4327 8.04725 20.8765 8.07803C21.5288 8.12327 21.9886 8.17235 22.2913 8.24003C22.3371 8.25027 22.3765 8.26035 22.4102 8.27001C22.3896 8.29619 22.3651 8.32572 22.336 8.35877C22.1328 8.58945 21.7914 8.89714 21.2913 9.31475C20.9474 9.60184 20.5299 9.93955 20.0459 10.331C19.1625 11.0455 18.0577 11.9391 16.7762 13.0302L16.543 13.2288L16.614 13.5267C17.0045 15.1663 17.3689 16.5406 17.6601 17.6391C17.819 18.2381 17.956 18.7552 18.0638 19.1886C18.2209 19.8206 18.3149 20.2704 18.3428 20.5768C18.347 20.6222 18.3493 20.6616 18.3505 20.6957C18.3176 20.6838 18.2798 20.6688 18.2367 20.6502C17.9532 20.5277 17.5539 20.2981 17.0014 19.9522C16.6264 19.7175 16.1833 19.4306 15.671 19.099C14.7143 18.4797 13.5162 17.7042 12.0695 16.8199L11.8087 16.6604L11.5478 16.82C10.0753 17.7209 8.86032 18.5085 7.89223 19.136C7.3851 19.4648 6.94572 19.7496 6.57253 19.9838C6.01576 20.3332 5.61353 20.5656 5.32808 20.6899C5.28721 20.7077 5.25111 20.7222 5.21941 20.7339C5.22088 20.7009 5.22355 20.663 5.22783 20.6197C5.25839 20.3111 5.35605 19.8582 5.51781 19.2225C5.62627 18.7962 5.76269 18.2914 5.92018 17.7087C6.22053 16.5972 6.59748 15.2024 7.00309 13.5286L7.07553 13.2297L6.84141 13.0303C5.52399 11.9079 4.39683 10.9982 3.50024 10.2747C3.03915 9.90254 2.63904 9.57963 2.30539 9.30232C1.80195 8.88388 1.45729 8.57562 1.25116 8.34437C1.22315 8.31293 1.19929 8.28466 1.17903 8.25939C1.20999 8.25084 1.24557 8.24198 1.28628 8.233C1.58841 8.1663 2.048 8.11835 2.701 8.07418C3.1353 8.0448 3.65101 8.01744 4.24568 7.98589C5.39523 7.9249 6.83989 7.84824 8.56208 7.71111L8.86638 7.68688L8.98388 7.40514C9.61646 5.88824 10.1238 4.58366 10.5314 3.53571C10.7656 2.93365 10.9668 2.4163 11.1399 1.99205C11.3854 1.39027 11.5751 0.972355 11.7339 0.708729C11.7601 0.66516 11.7838 0.628777 11.8048 0.598565C11.8256 0.628571 11.849 0.664658 11.8748 0.707817C12.0327 0.971308 12.2212 1.38911 12.465 1.99089C12.6368 2.41509 12.8365 2.93242 13.0689 3.53445C13.4735 4.58244 13.9771 5.88709 14.6053 7.404Z" fill="#F2C94C" stroke="#DCBB58" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
starred: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isFavorite: false,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.isFavorite = this.starred;
|
||||
},
|
||||
|
||||
store(toggle) {
|
||||
axios.post('people/' + this.hash + '/favorite', {'toggle': toggle})
|
||||
.then(response => {
|
||||
this.isFavorite = response.data.is_starred;
|
||||
|
||||
this.$notify({
|
||||
group: 'favorite',
|
||||
title: this.$t('app.default_save_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
304
resources/js/components/people/StayInTouch.vue
Normal file
304
resources/js/components/people/StayInTouch.vue
Normal file
@@ -0,0 +1,304 @@
|
||||
<style scoped>
|
||||
.error {
|
||||
animation-name: shakeError;
|
||||
animation-fill-mode: forwards;
|
||||
animation-duration: .6s;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
.form-group-error {
|
||||
display: block;
|
||||
color:#f57f6c;
|
||||
}
|
||||
|
||||
.dashed {
|
||||
border-bottom-style: dashed;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="di">
|
||||
<notifications group="main" position="bottom right" />
|
||||
|
||||
<!-- Contact doesn't have a frequency set -->
|
||||
<a v-if="!isActive" class="pointer" href="" @click.prevent="showUpdate">
|
||||
{{ $t('people.stay_in_touch_modal_title') }}
|
||||
</a>
|
||||
|
||||
<!-- Contact has a frequency set -->
|
||||
<div v-else class="di">
|
||||
<span v-tooltip.bottom="$t('people.stay_in_touch_next_date', { date: formatDate(nextTriggerDate) })" class="bb dashed dib pointer nowrap-link">
|
||||
{{ $tc('people.stay_in_touch_frequency', frequencyInput, { count: frequencyInput }) }}
|
||||
</span>
|
||||
<a class="pointer" href="" @click.prevent="showUpdate">
|
||||
{{ $t('app.edit') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<sweet-modal ref="updateModal" overlay-theme="dark" :title="$t('people.stay_in_touch_modal_title')">
|
||||
<div class="tc mw-100">
|
||||
<svg viewBox="0 0 423 74" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs />
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group-6" transform="translate(2.000000, 2.000000)">
|
||||
<g id="Group-3" transform="translate(341.519872, 38.183805) rotate(17.000000) translate(-341.519872, -38.183805) translate(324.519872, 20.183805)">
|
||||
<path id="Fill-45" d="M6.93842857,23.5330169 L2.60464286,22.0075932 C0.662392857,21.3235932 0.394035714,18.6742373 2.15839286,17.6107119 L29.2497143,1.28379661 C31.0863214,0.176949153 33.3461071,1.82745763 32.86525,3.92461017 L27.3171786,28.1343051 C26.9868929,29.5761356 25.466,30.3931525 24.0896071,29.8684068 L13.4160357,25.8010169 L23.88925,10.3942373 L6.93842857,23.5330169" fill="#BBE2EE" />
|
||||
<path id="Fill-46" d="M6.93842857,23.5330169 L7.13939286,22.9576271 L2.80560714,21.4315932 C2.39639286,21.2863729 2.10132143,21.0404746 1.89671429,20.7353898 C1.69271429,20.4309153 1.58585714,20.0642034 1.58585714,19.6968814 C1.59071429,19.087322 1.86332143,18.5033898 2.47107143,18.1342373 L29.5617857,1.80671186 C29.8762857,1.61816949 30.1895714,1.53945763 30.4961786,1.53884746 C30.96975,1.53823729 31.4323929,1.73715254 31.7723929,2.06725424 C32.1117857,2.39918644 32.3230714,2.84522034 32.3236786,3.35471186 C32.3236786,3.49322034 32.3078929,3.63783051 32.2732857,3.7879322 L26.7258214,27.9970169 C26.5303214,28.8555254 25.7695714,29.4168814 24.9505357,29.4181017 C24.7374286,29.4181017 24.5206786,29.3802712 24.3045357,29.2978983 L14.35225,25.5050847 L24.3901429,10.7383729 C24.5589286,10.4906441 24.5231071,10.1562712 24.30575,9.9500339 C24.0883929,9.74440678 23.7544643,9.72732203 23.5182857,9.91098305 L6.56807143,23.0497627 L6.93842857,23.5330169 L7.13939286,22.9576271 L6.93842857,23.5330169 L7.30939286,24.0162712 L21.2275357,13.2278644 L12.9145357,25.4568814 C12.8095,25.6112542 12.7803571,25.804678 12.8368214,25.9834576 C12.8926786,26.1622373 13.02625,26.3050169 13.2005,26.3715254 L23.8740714,30.4389153 C24.2280357,30.5737627 24.5935357,30.6384407 24.9505357,30.6384407 C26.3190357,30.6390508 27.5800714,29.7 27.9091429,28.2709831 L33.4566071,4.06128814 C33.51125,3.82271186 33.5379643,3.5859661 33.5379643,3.35471186 C33.5385714,2.49559322 33.1730714,1.72922034 32.6163214,1.18983051 C32.0595714,0.649220339 31.3048929,0.319728814 30.4961786,0.318508475 C29.9758571,0.317898305 29.4355,0.459457627 28.9370357,0.760271186 L1.84632143,17.0871864 C0.8585,17.679661 0.366714286,18.7047458 0.371571429,19.6968814 C0.371571429,20.300339 0.545214286,20.9013559 0.88825,21.4157288 C1.23067857,21.9301017 1.74857143,22.3535593 2.40367857,22.5829831 L6.73807143,24.1090169 C6.93114286,24.1773559 7.14728571,24.1419661 7.30939286,24.0162712 L6.93842857,23.5330169" fill="#5CBBDE" />
|
||||
<polyline id="Fill-47" fill="#BBE2EE" points="12.2703571 34.9425763 13.4160357 25.8010169 23.88925 10.3942373 6.93842857 23.5330169 12.2703571 34.9425763" />
|
||||
<path id="Fill-48" d="M12.2703571,34.9425763 L12.8726429,35.0188475 L14.0001071,26.0231186 L24.3901429,10.7383729 C24.5589286,10.4906441 24.5231071,10.1562712 24.30575,9.9500339 C24.0883929,9.74440678 23.7544643,9.72732203 23.5182857,9.91098305 L6.56807143,23.0497627 C6.34282143,23.2242712 6.26814286,23.5342373 6.38896429,23.792339 L11.7202857,35.2018983 C11.8350357,35.4471864 12.0985357,35.5875254 12.3650714,35.5448136 C12.631,35.5027119 12.8386429,35.287322 12.8726429,35.0188475 L12.2703571,34.9425763 L12.8198214,34.6832542 L7.69553571,23.7172881 L21.2275357,13.2278644 L12.9145357,25.4568814 C12.8605,25.5362034 12.8252857,25.6289492 12.8131429,25.7247458 L11.6674643,34.8663051 L12.2703571,34.9425763 L12.8198214,34.6832542 L12.2703571,34.9425763" fill="#5CBBDE" />
|
||||
<polyline id="Fill-49" fill="#BBE2EE" points="12.2703571 34.9425763 18.1013571 27.5863729 13.4160357 25.8010169 12.2703571 34.9425763" />
|
||||
<path id="Fill-50" d="M12.2703571,34.9425763 L12.7451429,35.3227119 L18.5761429,27.9665085 C18.6975714,27.8133559 18.73825,27.6138305 18.6866429,27.424678 C18.6356429,27.2361356 18.4990357,27.0854237 18.3168929,27.0158644 L13.6309643,25.2305085 C13.4567143,25.164 13.2624286,25.1810847 13.10275,25.2781017 C12.9430714,25.3745085 12.8368214,25.5386441 12.8131429,25.7247458 L11.6674643,34.8663051 C11.6334643,35.1366102 11.7840357,35.3989831 12.0341786,35.5045424 C12.2843214,35.6107119 12.57575,35.535661 12.7451429,35.3227119 L12.2703571,34.9425763 L12.8726429,35.0188475 L13.9217857,26.6461017 L17.1080714,27.860339 L11.7949643,34.5624407 L12.2703571,34.9425763 L12.8726429,35.0188475 L12.2703571,34.9425763" fill="#5CBBDE" />
|
||||
</g>
|
||||
<g id="Group-2" transform="translate(275.500000, 38.000000) rotate(-4.000000) translate(-275.500000, -38.000000) translate(256.000000, 20.000000)">
|
||||
<path id="Fill-97" d="M15.5451562,17.9652 C15.5451562,16.9386 16.3921875,16.1064 17.4342188,16.1064 L30.1275,16.1064 L30.1275,3.1872 C30.1275,1.8618 29.0367188,0.7872 27.69,0.7872 L5.143125,0.7872 C3.79640625,0.7872 2.705625,1.8618 2.705625,3.1872 L2.705625,22.3872 L1.22484375,27.4848 C0.90796875,28.5768 2.169375,29.451 3.1078125,28.791 L8.799375,24.7872 L15.5451562,24.7872 L15.5451562,17.9652" fill="#F5C894" />
|
||||
<path id="Fill-98" d="M15.5451562,17.9652 L16.1545312,17.9652 C16.1545312,17.2698 16.7273438,16.7076 17.4342188,16.7058 L30.1275,16.7058 C30.2859375,16.7058 30.444375,16.6422 30.5601563,16.5306 C30.6698437,16.4184 30.736875,16.2642 30.736875,16.1064 L30.736875,3.1872 C30.736875,1.53 29.371875,0.1878 27.69,0.1872 L5.143125,0.1872 C3.46125,0.1878 2.09625,1.53 2.09625,3.1872 L2.09625,22.3032 L0.63984375,27.3198 C0.59109375,27.4896 0.56671875,27.6606 0.56671875,27.828 C0.56671875,28.338 0.7921875,28.794 1.12734375,29.1084 C1.4625,29.4246 1.9134375,29.6184 2.4009375,29.6196 C2.76046875,29.6202 3.13828125,29.5092 3.46125,29.2794 L8.994375,25.3872 L15.5451562,25.3872 C15.7035937,25.3872 15.8620312,25.323 15.9717188,25.2114 C16.0875,25.0998 16.1545312,24.945 16.1545312,24.7872 L16.1545312,17.9652 L14.9357812,17.9652 L14.9357812,24.1872 L8.799375,24.1872 C8.67140625,24.1872 8.54953125,24.2262 8.4459375,24.2988 L2.754375,28.3026 C2.62640625,28.3896 2.51671875,28.4184 2.4009375,28.4196 C2.24859375,28.4202 2.09015625,28.3542 1.974375,28.2432 C1.8525,28.1304 1.78546875,27.99 1.78546875,27.828 C1.78546875,27.774 1.7915625,27.7152 1.8159375,27.6498 L3.290625,22.5516 C3.30890625,22.4976 3.315,22.443 3.315,22.3872 L3.315,3.1872 C3.32109375,2.1936 4.13765625,1.389 5.143125,1.3872 L27.69,1.3872 C28.7015625,1.389 29.518125,2.1936 29.518125,3.1872 L29.518125,15.5058 L17.4342188,15.5064 C16.0509375,15.5064 14.9357812,16.6068 14.9357812,17.9652 L15.5451562,17.9652" fill="#E5742B" />
|
||||
<path id="Fill-99" d="M34.9842187,16.1064 L17.4342188,16.1064 C16.3921875,16.1064 15.5451562,16.9386 15.5451562,17.9652 L15.5451562,31.047 C15.5451562,32.0736 16.3921875,32.9058 17.4342188,32.9058 L32.6076562,32.9058 L36.001875,34.9116 C36.9585937,35.4756 38.1164062,34.5876 37.7934375,33.5382 L36.8732812,30.5058 L36.8732812,17.9652 C36.8732812,16.9386 36.02625,16.1064 34.9842187,16.1064" fill="#BBE2EE" />
|
||||
<path id="Fill-100" d="M34.9842188,16.1064 L34.9842188,15.5064 L17.4342188,15.5064 C16.0509375,15.5064 14.9357812,16.6068 14.9357812,17.9652 L14.9357812,31.047 C14.9357812,32.4054 16.0509375,33.5058 17.4342188,33.5058 L32.4370313,33.5058 L35.685,35.4264 C35.9835937,35.6022 36.3126562,35.6874 36.6295313,35.6868 C37.123125,35.6862 37.5740625,35.4888 37.903125,35.1708 C38.2382813,34.8534 38.4576563,34.3998 38.4576563,33.894 C38.4576563,33.7206 38.4332812,33.5424 38.3784375,33.366 L37.4826563,30.417 L37.4826563,17.9652 C37.4826563,16.6068 36.3614063,15.5064 34.9842188,15.5064 L34.9842188,16.7058 C35.6910937,16.7076 36.2639063,17.2698 36.2639063,17.9652 L36.2639063,30.5058 C36.2639063,30.5622 36.27,30.624 36.2882812,30.678 L37.2145312,33.7104 C37.2328125,33.7764 37.2389063,33.837 37.2389063,33.894 C37.2389063,34.0542 37.171875,34.197 37.0560937,34.3098 C36.9403125,34.422 36.781875,34.488 36.6295313,34.4868 C36.5259375,34.4862 36.4284375,34.4634 36.3126562,34.3974 L32.9184375,32.3916 C32.8270313,32.3358 32.7173438,32.3058 32.6076563,32.3058 L17.4342188,32.3058 C16.7273438,32.3046 16.1545313,31.7418 16.1545313,31.047 L16.1545313,17.9652 C16.1545313,17.2698 16.7273438,16.7076 17.4342188,16.7058 L34.9842188,16.7058 L34.9842188,16.1064" fill="#5CBBDE" />
|
||||
<path id="Fill-101" d="M22.1446875,24.3966 C22.1446875,25.059 21.59625,25.5966 20.9259375,25.5966 C20.2495313,25.5966 19.7071875,25.059 19.7071875,24.3966 C19.7071875,23.7336 20.2495313,23.1966 20.9259375,23.1966 C21.59625,23.1966 22.1446875,23.7336 22.1446875,24.3966" fill="#FFFFFE" />
|
||||
<path id="Fill-102" d="M27.6290625,24.3966 C27.6290625,25.059 27.080625,25.5966 26.4103125,25.5966 C25.7339063,25.5966 25.1915625,25.059 25.1915625,24.3966 C25.1915625,23.7336 25.7339063,23.1966 26.4103125,23.1966 C27.080625,23.1966 27.6290625,23.7336 27.6290625,24.3966" fill="#FFFFFE" />
|
||||
<path id="Fill-103" d="M33.1134375,24.3966 C33.1134375,25.059 32.565,25.5966 31.8946875,25.5966 C31.2182813,25.5966 30.6759375,25.059 30.6759375,24.3966 C30.6759375,23.7336 31.2182813,23.1966 31.8946875,23.1966 C32.565,23.1966 33.1134375,23.7336 33.1134375,24.3966" fill="#FFFFFE" />
|
||||
</g>
|
||||
<g id="Group" transform="translate(120.210360, 37.580603) rotate(-14.000000) translate(-120.210360, -37.580603) translate(107.210360, 18.580603)">
|
||||
<path id="Fill-130" d="M22.6586977,37.0813651 L3.30986047,37.0813651 C1.97418605,37.0813651 0.891255814,36.0010794 0.891255814,34.6686667 L0.891255814,3.3035873 C0.891255814,1.9711746 1.97418605,0.890888889 3.30986047,0.890888889 L22.6586977,0.890888889 C23.9943721,0.890888889 25.0773023,1.9711746 25.0773023,3.3035873 L25.0773023,34.6686667 C25.0773023,36.0010794 23.9943721,37.0813651 22.6586977,37.0813651" fill="#FCE499" />
|
||||
<path id="Fill-131" d="M22.6586977,37.0813651 L22.6586977,36.4781905 L3.30986047,36.4781905 C2.30795349,36.476381 1.49772093,35.6675238 1.49590698,34.6686667 L1.49590698,3.3035873 C1.49772093,2.30412698 2.30795349,1.49587302 3.30986047,1.49406349 L22.6586977,1.49406349 C23.66,1.49587302 24.4708372,2.30412698 24.4726512,3.3035873 L24.4726512,34.6686667 C24.4708372,35.6675238 23.66,36.476381 22.6586977,36.4781905 L22.6586977,37.6845397 C24.3287442,37.6839365 25.6813488,36.3340317 25.6819535,34.6686667 L25.6819535,3.3035873 C25.6813488,1.63761905 24.3287442,0.287714286 22.6586977,0.287714286 L3.30986047,0.287714286 C1.63981395,0.287714286 0.287209302,1.63761905 0.286604651,3.3035873 L0.286604651,34.6686667 C0.287209302,36.3340317 1.63981395,37.6839365 3.30986047,37.6845397 L22.6586977,37.6845397 L22.6586977,37.0813651" fill="#F5BB27" />
|
||||
<path id="Fill-132" d="M5.72846512,26.223619 L5.72846512,6.92203175 C5.72846512,6.25612698 6.26962791,5.71568254 6.93776744,5.71568254 L19.0307907,5.71568254 C19.6989302,5.71568254 20.240093,6.25612698 20.240093,6.92203175 L20.240093,26.223619 C20.240093,26.890127 19.6989302,27.4299683 19.0307907,27.4299683 L6.93776744,27.4299683 C6.26962791,27.4299683 5.72846512,26.890127 5.72846512,26.223619" fill="#BBE2EE" />
|
||||
<path id="Fill-133" d="M5.72846512,26.223619 L6.33311628,26.223619 L6.33311628,6.92203175 C6.33372093,6.58907937 6.604,6.31946032 6.93776744,6.31885714 L19.0307907,6.31885714 C19.3645581,6.31946032 19.6348372,6.58907937 19.6354419,6.92203175 L19.6354419,26.223619 C19.6348372,26.5565714 19.3645581,26.8261905 19.0307907,26.8267937 L6.93776744,26.8267937 C6.604,26.8261905 6.33372093,26.5565714 6.33311628,26.223619 L5.12381395,26.223619 C5.1244186,27.2230794 5.93586047,28.0331429 6.93776744,28.0331429 L19.0307907,28.0331429 C20.0326977,28.0331429 20.8441395,27.2230794 20.8447442,26.223619 L20.8447442,6.92203175 C20.8441395,5.92257143 20.0326977,5.11311111 19.0307907,5.11250794 L6.93776744,5.11250794 C5.93586047,5.11311111 5.1244186,5.92257143 5.12381395,6.92203175 L5.12381395,26.223619 L5.72846512,26.223619" fill="#5CBBDE" />
|
||||
<path id="Fill-134" d="M16.0075349,33.3615873 L9.96102326,33.3615873 C9.29288372,33.3615873 8.75172093,32.8211429 8.75172093,32.1552381 C8.75172093,31.4887302 9.29288372,30.9488889 9.96102326,30.9488889 L16.0075349,30.9488889 C16.6750698,30.9488889 17.2168372,31.4887302 17.2168372,32.1552381 C17.2168372,32.8211429 16.6750698,33.3615873 16.0075349,33.3615873" fill="#BBE2EE" />
|
||||
<path id="Fill-135" d="M16.0075349,33.3615873 L16.0075349,32.7584127 L9.96102326,32.7584127 C9.62725581,32.7578095 9.35697674,32.4881905 9.35637209,32.1552381 C9.35697674,31.8222857 9.62725581,31.5526667 9.96102326,31.5520635 L16.0075349,31.5520635 C16.3406977,31.5526667 16.6115814,31.8222857 16.612186,32.1552381 C16.6115814,32.4881905 16.3406977,32.7578095 16.0075349,32.7584127 L16.0075349,33.9647619 C17.0094419,33.9641587 17.8208837,33.1546984 17.8214884,32.1552381 C17.8208837,31.1557778 17.0094419,30.3463175 16.0075349,30.3457143 L9.96102326,30.3457143 C8.95851163,30.3463175 8.14706977,31.1557778 8.14706977,32.1552381 C8.14706977,33.1546984 8.95851163,33.9641587 9.96102326,33.9647619 L16.0075349,33.9647619 L16.0075349,33.3615873" fill="#5CBBDE" />
|
||||
</g>
|
||||
<g id="Group-4" transform="translate(195.373740, 35.038750) rotate(6.000000) translate(-195.373740, -35.038750) translate(174.873740, 16.038750)">
|
||||
<path id="Fill-186" d="M38.673403,30.1261587 L31.5039104,20.0615873 C31.0455672,19.4186032 30.2977761,19.0355873 29.5004179,19.0355873 L25.1580896,19.0355873 L25.1580896,15.4165397 C25.1580896,14.7500317 24.609791,14.2101905 23.934209,14.2101905 C23.2580149,14.2101905 22.7103284,14.7500317 22.7103284,15.4165397 L22.7103284,19.0355873 L18.8098209,19.0355873 L18.8098209,15.4165397 C18.8098209,14.7500317 18.2615224,14.2101905 17.5859403,14.2101905 C16.9097463,14.2101905 16.3620597,14.7500317 16.3620597,15.4165397 L16.3620597,19.0355873 L12.0191194,19.0355873 C11.2217612,19.0355873 10.4745821,19.4186032 10.0162388,20.0615873 L2.84613433,30.1261587 C2.55668657,30.5326984 2.40186567,31.0164444 2.40186567,31.5128571 L2.40186567,34.718127 C2.40186567,36.0505397 3.49785075,37.1308254 4.84962687,37.1308254 L36.6705224,37.1308254 C38.0222985,37.1308254 39.1182836,36.0505397 39.1182836,34.718127 L39.1182836,31.5128571 C39.1182836,31.0164444 38.9628507,30.5326984 38.673403,30.1261587" fill="#BBE2EE" />
|
||||
<path id="Fill-187" d="M38.673403,30.1261587 L39.1745821,29.7799365 L32.0044776,19.7147619 C31.4317015,18.9107302 30.4972687,18.4324127 29.5004179,18.4324127 L25.1580896,18.4324127 L25.1580896,19.0355873 L25.7700299,19.0355873 L25.7700299,15.4165397 C25.7694179,14.4164762 24.948194,13.6070159 23.934209,13.6070159 C22.9196119,13.6070159 22.0983881,14.4164762 22.0983881,15.4165397 L22.0983881,19.0355873 L22.7103284,19.0355873 L22.7103284,18.4324127 L18.8098209,18.4324127 L18.8098209,19.0355873 L19.4217612,19.0355873 L19.4217612,15.4165397 C19.4211493,14.4164762 18.5999254,13.6070159 17.5859403,13.6070159 C16.5713433,13.6070159 15.7501194,14.4164762 15.7501194,15.4165397 L15.7501194,19.0355873 L16.3620597,19.0355873 L16.3620597,18.4324127 L12.0191194,18.4324127 C11.0222687,18.4324127 10.0878358,18.9107302 9.5150597,19.7153651 L2.34556716,29.7799365 C1.98391045,30.2872063 1.78992537,30.8927937 1.78992537,31.5128571 L1.78992537,34.718127 C1.78992537,36.3840952 3.15944776,37.7333968 4.84962687,37.734 L36.6705224,37.734 C38.3607015,37.7333968 39.7296119,36.3840952 39.7302239,34.718127 L39.7302239,31.5128571 C39.7302239,30.8927937 39.5362388,30.2872063 39.1745821,29.7799365 L38.673403,30.1261587 L38.1728358,30.4729841 C38.3894627,30.7775873 38.5063433,31.1406984 38.5063433,31.5128571 L38.5063433,34.718127 C38.5045075,35.7169841 37.6838955,36.5258413 36.6705224,36.5276508 L4.84962687,36.5276508 C3.83564179,36.5258413 3.01564179,35.7169841 3.01380597,34.718127 L3.01380597,31.5128571 C3.01380597,31.1406984 3.13007463,30.7775873 3.34670149,30.4729841 L10.516806,20.4084127 C10.8607164,19.925873 11.4212537,19.6387619 12.0191194,19.6387619 L16.3620597,19.6387619 C16.523,19.6387619 16.6802687,19.5742222 16.7947015,19.4620317 C16.9085224,19.3498413 16.974,19.1942222 16.974,19.0355873 L16.974,15.4165397 C16.9746119,15.0835873 17.2481493,14.8139683 17.5859403,14.8133651 C17.9231194,14.8139683 18.1972687,15.0835873 18.1978806,15.4165397 L18.1978806,19.0355873 C18.1978806,19.1942222 18.2627463,19.3498413 18.3765672,19.4620317 C18.491,19.5742222 18.6482687,19.6387619 18.8098209,19.6387619 L22.7103284,19.6387619 C22.8712687,19.6387619 23.0291493,19.5742222 23.1429701,19.4620317 C23.256791,19.3498413 23.3222687,19.1942222 23.3222687,19.0355873 L23.3222687,15.4165397 C23.3228806,15.0835873 23.5964179,14.8139683 23.934209,14.8133651 C24.2713881,14.8139683 24.5455373,15.0835873 24.5461493,15.4165397 L24.5461493,19.0355873 C24.5461493,19.1942222 24.6110149,19.3498413 24.7248358,19.4620317 C24.8392687,19.5742222 24.9965373,19.6387619 25.1580896,19.6387619 L29.5004179,19.6387619 C30.0982836,19.6387619 30.6594328,19.925873 31.0027313,20.4084127 L38.1728358,30.4729841 L38.673403,30.1261587" fill="#5CBBDE" />
|
||||
<path id="Fill-188" d="M40.3360448,10.8794603 C40.3360448,10.8794603 41.2612985,0.940349206 20.7600746,0.940349206 L20.7594627,0.970507937 L20.7594627,0.940349206 C0.314537313,0.940349206 1.18349254,10.8794603 1.18349254,10.8794603 L1.18349254,13.148 C1.18349254,13.7348889 1.78870149,14.2101905 2.53465672,14.2101905 L7.93808955,14.2101905 C8.68404478,14.2101905 9.28864179,13.7348889 9.28864179,13.148 L9.28864179,8.96980952 C9.28864179,8.96980952 13.919806,7.45765079 20.7594627,7.45765079 C27.5997313,7.45765079 32.2308955,8.96980952 32.2308955,8.96980952 L32.2308955,13.148 C32.2308955,13.7348889 32.8354925,14.2101905 33.5820597,14.2101905 L38.9848806,14.2101905 C39.7314478,14.2101905 40.3360448,13.7348889 40.3360448,13.148 L40.3360448,10.8794603" fill="#BBE2EE" />
|
||||
<path id="Fill-189" d="M40.3360448,10.8794603 L40.9455373,10.9343492 C40.9467612,10.9174603 40.9541045,10.8312063 40.9541045,10.6864444 C40.9541045,10.2847302 40.8984179,9.4312381 40.5012687,8.37025397 C39.9089104,6.77847619 38.5247015,4.73190476 35.5108955,3.12866667 C32.4970896,1.52180952 27.8787761,0.337777778 20.7600746,0.337174603 C20.427791,0.337174603 20.1554776,0.60015873 20.1487463,0.92768254 L20.1481343,0.95784127 L20.7594627,0.970507937 L21.371403,0.963873016 L21.371403,0.933714286 C21.3677313,0.602571429 21.0954179,0.337174603 20.7594627,0.337174603 C16.0022388,0.337174603 12.3642537,0.869174603 9.57319403,1.7027619 C5.38752239,2.94952381 3.10008955,4.89657143 1.89456716,6.70247619 C0.687208955,8.50657143 0.566044776,10.1297143 0.566044776,10.705746 C0.566044776,10.8372381 0.572776119,10.9156508 0.574,10.9319365 C0.601537313,11.2437778 0.865895522,11.4826349 1.18349254,11.4826349 L1.18349254,10.8794603 L1.06355224,10.2883492 C0.777164179,10.3444444 0.571552239,10.591746 0.571552239,10.8794603 L0.571552239,13.148 C0.570940299,13.652254 0.836522388,14.0901587 1.19756716,14.3706349 C1.5610597,14.6553333 2.02980597,14.8127619 2.53465672,14.8133651 L7.93808955,14.8133651 C8.4429403,14.8127619 8.91107463,14.6553333 9.27456716,14.3706349 C9.63622388,14.0901587 9.90119403,13.652254 9.90058209,13.148 L9.90058209,8.96980952 L9.28864179,8.96980952 L9.48140299,9.5428254 L9.52668657,9.52834921 C10.0186866,9.37453968 14.4332239,8.06022222 20.7594627,8.0608254 C24.1337015,8.0608254 26.9645373,8.43419048 28.946,8.80634921 C29.9367313,8.99212698 30.7151194,9.17790476 31.2432239,9.31663492 C31.5069701,9.386 31.7082985,9.44330159 31.8429254,9.48250794 C31.9096269,9.5024127 31.959806,9.51809524 31.9928507,9.52834921 L32.0381343,9.5428254 L32.2308955,8.96980952 L31.6189552,8.96980952 L31.6189552,13.148 C31.6183433,13.652254 31.8839254,14.0901587 32.2449701,14.3706349 C32.6084627,14.6553333 33.076597,14.8127619 33.5820597,14.8133651 L38.9848806,14.8133651 C39.4903433,14.8127619 39.9584776,14.6553333 40.3219701,14.3706349 C40.6830149,14.0901587 40.948597,13.652254 40.9479851,13.148 L40.9479851,10.8794603 C40.9479851,10.591746 40.7423731,10.3444444 40.4559851,10.2883492 L40.3360448,10.8794603 L40.3360448,11.4826349 C40.651806,11.4826349 40.9167761,11.244381 40.9455373,10.9343492 L40.3360448,10.8794603 L40.3360448,10.2762857 C40.0221194,10.2762857 39.7577612,10.512127 39.7271642,10.8203492 C39.6959552,11.1279683 39.9082985,11.410254 40.2161045,11.4711746 L40.3360448,10.8794603 L39.7241045,10.8794603 L39.7241045,13.148 C39.7234925,13.2300317 39.6867761,13.3235238 39.5588806,13.4272698 C39.4334328,13.5273968 39.2259851,13.607619 38.9848806,13.6070159 L33.5820597,13.6070159 C33.3403433,13.607619 33.1335075,13.5273968 33.0080597,13.4272698 C32.8801642,13.3235238 32.8434478,13.2306349 32.8428358,13.148 L32.8428358,8.96980952 C32.8428358,8.7092381 32.6739403,8.47942857 32.4230448,8.39739683 C32.380209,8.38412698 27.6915224,6.85507937 20.7594627,6.85447619 C13.8280149,6.85507937 9.13932836,8.38412698 9.09649254,8.39739683 C8.84559701,8.47942857 8.67670149,8.7092381 8.67670149,8.96980952 L8.67670149,13.148 C8.67608955,13.2306349 8.63937313,13.3235238 8.51147761,13.4272698 C8.38602985,13.5273968 8.17919403,13.607619 7.93808955,13.6070159 L2.53465672,13.6070159 C2.29355224,13.607619 2.08610448,13.5273968 1.96065672,13.4272698 C1.83276119,13.3235238 1.79604478,13.2300317 1.79543284,13.148 L1.79543284,10.8794603 L1.18349254,10.8794603 L1.30343284,11.4711746 L1.30404478,11.4711746 C1.61185075,11.410254 1.82358209,11.1279683 1.79298507,10.8203492 C1.76177612,10.512127 1.49741791,10.2762857 1.18349254,10.2762857 L1.18349254,10.8794603 L1.79298507,10.8275873 L1.74770149,10.8312063 L1.79359701,10.8281905 L1.79298507,10.8275873 L1.74770149,10.8312063 L1.79359701,10.8281905 C1.79237313,10.8185397 1.78992537,10.7763175 1.78992537,10.705746 C1.78992537,10.4300952 1.82908955,9.7255873 2.16259701,8.82987302 C2.66683582,7.48660317 3.80932836,5.70301587 6.57591045,4.2107619 C9.34310448,2.72152381 13.7613134,1.54292063 20.7594627,1.54352381 L20.7594627,0.940349206 L20.1475224,0.946984127 L20.1475224,0.977142857 C20.151194,1.30647619 20.4222836,1.57187302 20.756403,1.57368254 C21.0911343,1.57488889 21.3646716,1.31190476 21.371403,0.982571429 L21.3720149,0.953015873 L20.7600746,0.940349206 L20.7600746,1.54352381 C25.4151045,1.54292063 28.9313134,2.06165079 31.5767313,2.84577778 C35.5463881,4.02619048 37.5492687,5.7832381 38.5950746,7.32796825 C39.6384328,8.87390476 39.7302239,10.2479365 39.7302239,10.6864444 L39.7277761,10.8010476 L39.7265522,10.8245714 L39.7338955,10.8251746 L39.7265522,10.8245714 L39.7338955,10.8251746 L39.7265522,10.8245714 L40.3360448,10.8794603 L40.3360448,10.2762857 L40.3360448,10.8794603" fill="#5CBBDE" />
|
||||
<path id="Fill-190" d="M26.8794776,25.6705079 C26.8794776,27.335873 24.1398209,28.686381 20.7600746,28.686381 C17.3803284,28.686381 14.6406716,27.335873 14.6406716,25.6705079 C14.6406716,24.0051429 17.3803284,22.6546349 20.7600746,22.6546349 C24.1398209,22.6546349 26.8794776,24.0051429 26.8794776,25.6705079" fill="#FFFFFE" />
|
||||
<path id="Fill-191" d="M26.8794776,25.6705079 L26.2675373,25.6705079 C26.2663134,25.919619 26.1702388,26.1729524 25.9309701,26.4546349 C25.5760448,26.8744444 24.8906716,27.3008889 23.9856119,27.5994603 C23.0811642,27.9010476 21.9649851,28.0832063 20.7600746,28.0832063 C19.1531194,28.0844127 17.7028209,27.7568889 16.7065821,27.2634921 C16.2078507,27.0186032 15.826,26.7333016 15.5885672,26.4546349 C15.3492985,26.1729524 15.2532239,25.919619 15.2526119,25.6705079 C15.2532239,25.4213968 15.3492985,25.1680635 15.5885672,24.886381 C15.9434925,24.4665714 16.6288657,24.040127 17.5345373,23.7409524 C18.4383731,23.4399683 19.5545522,23.2578095 20.7600746,23.2578095 C22.3664179,23.256 23.8167164,23.584127 24.8135672,24.0775238 C25.3116866,24.3224127 25.6935373,24.6077143 25.9309701,24.886381 C26.1702388,25.1680635 26.2663134,25.4213968 26.2675373,25.6705079 L27.4914179,25.6705079 C27.4920299,25.0866349 27.2454179,24.5467937 26.865403,24.1070794 C26.2920149,23.4441905 25.4255075,22.9501905 24.3772537,22.5985397 C23.3277761,22.2493016 22.089209,22.0520635 20.7600746,22.0514603 C18.9866716,22.0532698 17.3772687,22.4000952 16.1588955,22.9984444 C15.5506269,23.2994286 15.0359851,23.663746 14.6541343,24.1070794 C14.2741194,24.5467937 14.0275075,25.0866349 14.0287313,25.6705079 C14.0275075,26.254381 14.2741194,26.7942222 14.6541343,27.2339365 C15.2281343,27.8968254 16.0946418,28.3908254 17.1428955,28.7424762 C18.1917612,29.0917143 19.4303284,29.2889524 20.7600746,29.2895556 C22.5328657,29.287746 24.1422687,28.9403175 25.3606418,28.3425714 C25.9695224,28.0415873 26.4841642,27.6772698 26.865403,27.2339365 C27.2454179,26.7942222 27.4920299,26.254381 27.4914179,25.6705079 L26.8794776,25.6705079" fill="#CCD4D3" />
|
||||
</g>
|
||||
<g id="Group-5" transform="translate(67.000000, 43.000000)" stroke="#F5BB27" stroke-linecap="square" stroke-width="3">
|
||||
<path id="Line" d="M0.277777778,5.5 L9.73854798,5.5" />
|
||||
<path id="Line" d="M5.5,10.6764706 L5.5,0.323529412" />
|
||||
</g>
|
||||
<g id="Group-5" transform="translate(42.000000, 2.000000)" stroke="#A2B88C" stroke-linecap="square" stroke-width="3">
|
||||
<path id="Line" d="M0.277777778,5.5 L9.73854798,5.5" />
|
||||
<path id="Line" d="M5.5,10.6764706 L5.5,0.323529412" />
|
||||
</g>
|
||||
<g id="Group-5" transform="translate(22.000000, 51.000000)" stroke="#E5742B" stroke-linecap="square" stroke-width="3">
|
||||
<path id="Line" d="M0.277777778,5.5 L9.73854798,5.5" />
|
||||
<path id="Line" d="M5.5,10.6764706 L5.5,0.323529412" />
|
||||
</g>
|
||||
<g id="Group-5" transform="translate(0.000000, 17.000000)" stroke="#5CBBDE" stroke-linecap="square" stroke-width="3">
|
||||
<path id="Line" d="M0.277777778,5.5 L9.73854798,5.5" />
|
||||
<path id="Line" d="M5.5,10.6764706 L5.5,0.323529412" />
|
||||
</g>
|
||||
<g id="Group-5" transform="translate(414.000000, 37.500000) scale(-1, -1) translate(-414.000000, -37.500000) translate(409.000000, 32.000000)" stroke="#F5BB27" stroke-linecap="square" stroke-width="3">
|
||||
<path id="Line" d="M0.277777778,5.5 L9.73854798,5.5" />
|
||||
<path id="Line" d="M5.5,10.6764706 L5.5,0.323529412" />
|
||||
</g>
|
||||
<g id="Group-5" transform="translate(386.000000, 5.500000) scale(-1, -1) translate(-386.000000, -5.500000) translate(381.000000, 0.000000)" stroke="#A2B88C" stroke-linecap="square" stroke-width="3">
|
||||
<path id="Line" d="M0.277777778,5.5 L9.73854798,5.5" />
|
||||
<path id="Line" d="M5.5,10.6764706 L5.5,0.323529412" />
|
||||
</g>
|
||||
<g id="Group-5" transform="translate(386.000000, 64.500000) scale(-1, -1) translate(-386.000000, -64.500000) translate(381.000000, 59.000000)" stroke="#E5742B" stroke-linecap="square" stroke-width="3">
|
||||
<path id="Line" d="M0.277777778,5.5 L9.73854798,5.5" />
|
||||
<path id="Line" d="M5.5,10.6764706 L5.5,0.323529412" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<form @submit.prevent="update()">
|
||||
<div class="mb4">
|
||||
<div v-if="limited" class="mt3 mb3 form-information-message br2">
|
||||
<div class="pa3 flex">
|
||||
<div class="mr3">
|
||||
<svg viewBox="0 0 20 20">
|
||||
<g fill-rule="evenodd">
|
||||
<circle cx="10" cy="10" r="9" fill="currentColor" /><path d="M10 0C4.486 0 0 4.486 0 10s4.486 10 10 10 10-4.486 10-10S15.514 0 10 0m0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8m1-5v-3a1 1 0 0 0-1-1H9a1 1 0 1 0 0 2v3a1 1 0 0 0 1 1h1a1 1 0 1 0 0-2m-1-5.9a1.1 1.1 0 1 0 0-2.2 1.1 1.1 0 0 0 0 2.2" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div v-html="$t('settings.personalisation_paid_upgrade_vue', {url: 'settings/subscriptions' })"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt3 b mb3" :class="[ dirltr ? 'tl' : 'tr' ]">
|
||||
{{ $t('people.stay_in_touch_modal_desc', { firstname: firstName }) }}
|
||||
</p>
|
||||
<div class="mb2">
|
||||
<toggle-button class="mr2" :sync="true" :labels="true" :value="stateInput" @change="stateInput = !stateInput" />
|
||||
<div class="dib relative" style="top: -2px;">
|
||||
<stay-in-touch-label
|
||||
v-model="frequencyInput"
|
||||
:class="{ 'form-group-error': $v.frequencyInput.$error }"
|
||||
>
|
||||
<div class="dib">
|
||||
<form-input
|
||||
:id="'frequency'"
|
||||
v-model.number="frequencyInput"
|
||||
:input-type="'number'"
|
||||
:width="60"
|
||||
:required="true"
|
||||
:validator="$v.frequencyInput"
|
||||
@input="onInput($event)"
|
||||
/>
|
||||
</div>
|
||||
</stay-in-touch-label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="errorMessage !== ''" class="form-error-message mb3">
|
||||
<div class="pa2">
|
||||
<p class="mb0">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button" class="tc">
|
||||
<a class="btn" href="" @click.prevent="closeModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a class="btn btn-primary" href="" @click.prevent="update()">
|
||||
{{ $t('app.save') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
import { ToggleButton } from 'vue-js-toggle-button';
|
||||
import { validationMixin } from 'vuelidate';
|
||||
import { required, numeric } from 'vuelidate/lib/validators';
|
||||
import StayInTouchLabel from './StayInTouchLabel';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
SweetModal,
|
||||
ToggleButton,
|
||||
StayInTouchLabel,
|
||||
},
|
||||
|
||||
mixins: [validationMixin],
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
firstName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
frequency: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
triggerDate: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
limited: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
validations: {
|
||||
frequencyInput: {
|
||||
required,
|
||||
numeric,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: false,
|
||||
errorMessage: '',
|
||||
frequencyInput: 0,
|
||||
nextTriggerDate: null,
|
||||
stateInput: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.isActive = (this.frequency > 0);
|
||||
|
||||
// record initial values when the component loads so we can
|
||||
// put those values back if user puts wrong values when updating
|
||||
// the counter
|
||||
this.stateInput = this.isActive;
|
||||
this.frequencyInput = this.frequency ?? 0;
|
||||
this.nextTriggerDate = this.triggerDate;
|
||||
},
|
||||
|
||||
formatDate(dateAsString) {
|
||||
const moment = require('moment-timezone');
|
||||
moment.locale(this._i18n.locale);
|
||||
moment.tz.setDefault('UTC');
|
||||
var date = moment.tz(moment(dateAsString), this.$root.timezone);
|
||||
return date.format('LL');
|
||||
},
|
||||
|
||||
showUpdate() {
|
||||
this.errorMessage = '';
|
||||
this.$refs.updateModal.open();
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
this.$refs.updateModal.close();
|
||||
},
|
||||
|
||||
update() {
|
||||
this.errorMessage = '';
|
||||
|
||||
// check if you need a subscription to access this feature
|
||||
if (this.limited) {
|
||||
this.errorMessage = this.$t('people.stay_in_touch_premium');
|
||||
return;
|
||||
}
|
||||
|
||||
this.$v.$touch();
|
||||
|
||||
if (this.$v.$invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
axios.post('people/' + this.hash + '/stayintouch', {frequency: this.frequencyInput, state: this.stateInput})
|
||||
.then(response => {
|
||||
this.$refs.updateModal.close();
|
||||
this.isActive = this.stateInput;
|
||||
this.nextTriggerDate = response.data.trigger_date;
|
||||
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('app.default_save_success'),
|
||||
text: '',
|
||||
width: '500px',
|
||||
type: 'success'
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.errorMessage = this.$t('app.error_save');
|
||||
});
|
||||
},
|
||||
|
||||
onInput(value) {
|
||||
this.stateInput = value > 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
20
resources/js/components/people/StayInTouchLabel.vue
Normal file
20
resources/js/components/people/StayInTouchLabel.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
|
||||
render(createElement) {
|
||||
const text = this.$tc('people.stay_in_touch_modal_label', this.value, {count: '[slot]'});
|
||||
const texts = _.split(text, '[slot]');
|
||||
return createElement('div', [
|
||||
createElement('span', texts[0]),
|
||||
this.$slots.default,
|
||||
createElement('span', texts[1]),
|
||||
]);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
231
resources/js/components/people/Tags.vue
Normal file
231
resources/js/components/people/Tags.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<style scoped>
|
||||
.autocomplete-results {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.autocomplete-result.is-active,
|
||||
.autocomplete-result:hover {
|
||||
background-color: #4AAE9B;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tag-link,
|
||||
.tag-link:hover{
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="tc">
|
||||
<!-- list of existing tags -->
|
||||
<ul>
|
||||
<li v-for="tag in contactTags" :key="tag.id" class="di mr2">
|
||||
<a v-if="!editMode" :href="`people?tags[]=${encodeURIComponent(tag.name)}`" class="tag-link bg-white ph2 pb1 pt0 dib br3 b--light-gray ba mb2">
|
||||
{{ tag.name }}
|
||||
</a>
|
||||
<span v-else class="bg-white ph2 pb1 pt0 dib br3 b--light-gray ba mb2">
|
||||
<span>
|
||||
{{ tag.name }}
|
||||
</span>
|
||||
<span class="pointer" @click="removeTag(tag)">
|
||||
×
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<!-- edit button -->
|
||||
<li v-show="contactTags.length > 0" class="di">
|
||||
<a v-show="!editMode" class="pointer" href="" @click.prevent="enterEditMode">
|
||||
{{ $t('app.edit') }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- add a new tag -->
|
||||
<li v-show="editMode" class="di mb3">
|
||||
<div class="relative di mr2">
|
||||
<input ref="tags"
|
||||
v-model="search"
|
||||
type="text"
|
||||
class="di br2 f5 ba b--black-40 pa2 outline-0"
|
||||
:placeholder="$t('people.tag_add_search')"
|
||||
@keydown.down="onArrowDown"
|
||||
@keydown.up="onArrowUp"
|
||||
@keydown.enter="onEnter"
|
||||
@keydown.esc="onEscape"
|
||||
@input="onChange"
|
||||
/>
|
||||
|
||||
<ul v-show="isOpen" v-if="results.length > 0" class="autocomplete-results ba b--gray-monica absolute bg-white left-0 z-9999">
|
||||
<li v-for="(result, i) in results"
|
||||
:key="i"
|
||||
class="autocomplete-result"
|
||||
:class="{ 'is-active': i === arrowCounter }"
|
||||
@click="setResult(result)"
|
||||
>
|
||||
{{ result.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<a class="pointer" href="" @click.prevent="search = ''; editMode = false; isOpen = false;">
|
||||
{{ $t('app.close') }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- case of no tags -->
|
||||
<li v-show="contactTags.length === 0 && !editMode" class="di">
|
||||
<span class="i mr2">
|
||||
{{ $t('people.tag_no_tags') }}
|
||||
</span>
|
||||
<a v-show="!editMode" class="pointer" href="" @click.prevent="enterEditMode">
|
||||
{{ $t('people.tag_add') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
|
||||
export default {
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allTags: [],
|
||||
availableTags: [],
|
||||
contactTags: [],
|
||||
editMode: false,
|
||||
search: '',
|
||||
results: [],
|
||||
isOpen: false,
|
||||
arrowCounter: 0,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
document.addEventListener('click', this.handleClickOutside);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getExistingTags();
|
||||
this.getContactTags();
|
||||
},
|
||||
|
||||
getExistingTags() {
|
||||
axios.get('tags')
|
||||
.then(response => {
|
||||
this.allTags = response.data.data;
|
||||
});
|
||||
},
|
||||
|
||||
getContactTags() {
|
||||
axios.get('people/' + this.hash + '/tags')
|
||||
.then(response => {
|
||||
this.contactTags = response.data.data;
|
||||
});
|
||||
},
|
||||
|
||||
enterEditMode() {
|
||||
this.editMode = true;
|
||||
this.$nextTick(() => this.$refs.tags.focus());
|
||||
},
|
||||
|
||||
removeTag(tag) {
|
||||
this.contactTags.splice(this.contactTags.indexOf(tag), 1);
|
||||
this.store();
|
||||
},
|
||||
|
||||
onChange() {
|
||||
this.isOpen = true;
|
||||
this.filterResults();
|
||||
},
|
||||
|
||||
onEnter() {
|
||||
if (this.search !== '') {
|
||||
this.contactTags.push({
|
||||
id: moment().format(), // we just need a random ID here
|
||||
name: this.search
|
||||
});
|
||||
this.arrowCounter = -1;
|
||||
this.isOpen = false;
|
||||
this.search = '';
|
||||
this.store();
|
||||
}
|
||||
},
|
||||
|
||||
onArrowDown() {
|
||||
if (this.arrowCounter < this.results.length) {
|
||||
this.arrowCounter = this.arrowCounter + 1;
|
||||
this.search = this.results[this.arrowCounter].name;
|
||||
}
|
||||
},
|
||||
|
||||
onArrowUp() {
|
||||
if (this.arrowCounter > 0) {
|
||||
this.arrowCounter = this.arrowCounter - 1;
|
||||
this.search = this.results[this.arrowCounter].name;
|
||||
}
|
||||
},
|
||||
|
||||
onEscape() {
|
||||
this.arrowCounter = -1;
|
||||
this.isOpen = false;
|
||||
this.search = '';
|
||||
},
|
||||
|
||||
setResult(result) {
|
||||
this.search = '';
|
||||
this.isOpen = false;
|
||||
this.contactTags.push(result);
|
||||
this.store();
|
||||
},
|
||||
|
||||
filterResults() {
|
||||
var me = this.contactTags;
|
||||
var search = _.toLower(this.search);
|
||||
this.results = this.allTags.filter(item => _.toLower(item.name).indexOf(search) > -1
|
||||
&& _.findIndex(me, t => t.name === item.name) < 0);
|
||||
},
|
||||
|
||||
filterAllTags() {
|
||||
var me = this.contactTags;
|
||||
this.availableTags = this.allTags.filter((item) => {
|
||||
return !me.includes(item);
|
||||
});
|
||||
},
|
||||
|
||||
store() {
|
||||
axios.post('people/' + this.hash + '/tags/update', this.contactTags)
|
||||
.then(response => {
|
||||
this.getExistingTags();
|
||||
});
|
||||
},
|
||||
|
||||
handleClickOutside(evt) {
|
||||
if (!this.$el.contains(evt.target)) {
|
||||
this.isOpen = false;
|
||||
this.arrowCounter = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
286
resources/js/components/people/Tasks.vue
Normal file
286
resources/js/components/people/Tasks.vue
Normal file
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<img src="img/people/tasks.svg" :alt="$t('people.tasks_title')" class="icon-section icon-tasks" />
|
||||
<h3>
|
||||
{{ $t('people.section_personal_tasks') }}
|
||||
|
||||
<span v-if="tasks.length !== 0" class="f6 pt2" :class="[ dirltr ? 'fr' : 'fl' ]">
|
||||
<a v-if="!editMode" v-cy-name="'task-toggle-edit-mode'" class="pointer" href="" @click.prevent="editMode = true">
|
||||
{{ $t('app.edit') }}
|
||||
</a>
|
||||
<a v-else v-cy-name="'task-toggle-edit-mode'" class="pointer" href="" @click.prevent="editMode = false">
|
||||
{{ $t('app.done') }}
|
||||
</a>
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div :class="[editMode ? 'bg-washed-yellow b--yellow ba pa2' : '']">
|
||||
<!-- EMPTY STATE -->
|
||||
<div v-if="tasks.length === 0 && !addMode" v-cy-name="'task-blank-state'" class="tc bg-near-white b--moon-gray pa3">
|
||||
<p>{{ $t('people.tasks_blank_title') }}</p>
|
||||
<p>
|
||||
<a v-cy-name="'add-task-button'" class="pointer" href="" @click.prevent="toggleAddMode">
|
||||
{{ $t('people.tasks_add_task') }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- LIST OF IN PROGRESS TASKS -->
|
||||
<ul v-cy-name="'tasks-body'" v-cy-items="inProgress(tasks).map(t => t.id)">
|
||||
<li v-for="(task, i) in inProgress(tasks)" :key="task.id" v-cy-name="'task-item-' + task.id">
|
||||
<form-checkbox
|
||||
v-model.lazy="task.completed"
|
||||
:disabled="task.disabled"
|
||||
:name="'task'"
|
||||
:dclass="[ dirltr ? 'mr1' : 'ml1' ]"
|
||||
@change="toggleComplete(task)"
|
||||
>
|
||||
{{ task.title }}
|
||||
<span v-if="task.description" class="silver ml3" dir="auto">
|
||||
{{ task.description }}
|
||||
</span>
|
||||
</form-checkbox>
|
||||
|
||||
<div v-if="editMode" class="di">
|
||||
<em class="fa fa-pencil-square-o pointer pr2 ml3 dark-blue" @click="toggleEditMode(task)"></em>
|
||||
<em v-cy-name="'task-delete-button-' + task.id" class="fa fa-trash-o pointer pr2 dark-blue" @click="trash(task)"></em>
|
||||
</div>
|
||||
|
||||
<!-- EDIT BOX -->
|
||||
<form v-show="task.edit" class="bg-near-white pa2 br2 mt3 mb3">
|
||||
<div>
|
||||
<label :for="'edit-title' + i" class="db fw6 lh-copy f6">
|
||||
{{ $t('people.tasks_form_title') }}
|
||||
</label>
|
||||
<input :id="'edit-title' + i" v-model="task.title" class="pa2 db w-100" type="text" @keyup.esc="editMode = false" />
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<label :for="'edit-description' + i" class="db fw6 lh-copy f6">
|
||||
{{ $t('people.tasks_form_description') }}
|
||||
</label>
|
||||
<textarea :id="'edit-description' + i"
|
||||
v-model="task.description"
|
||||
class="pa2 db w-100"
|
||||
type="text"
|
||||
@keyup.esc="editMode = false"
|
||||
>
|
||||
</textarea>
|
||||
</div>
|
||||
<div class="lh-copy mt3">
|
||||
<a class="btn btn-primary" href="" @click.prevent="update(task, true)">
|
||||
{{ $t('app.update') }}
|
||||
</a>
|
||||
<a class="btn" href="" @click.prevent="toggleEditMode(task)">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- ADD TASK TO ENTER ADD MODE -->
|
||||
<div v-if="!updateMode && !addMode && tasks.length !== 0" class="bg-near-white pa2 br2 mt3 mb3">
|
||||
<a class="pointer" href="" @click.prevent="toggleAddMode">
|
||||
{{ $t('people.tasks_add_task') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- ADD A TASK VIEW -->
|
||||
<div v-if="addMode" v-cy-name="'task-add-view'">
|
||||
<form class="bg-near-white pa2 br2 mt3 mb3">
|
||||
<div>
|
||||
<label for="add-title" class="db fw6 lh-copy f6">
|
||||
{{ $t('people.tasks_form_title') }}
|
||||
</label>
|
||||
<input id="add-title" v-model="newTask.title" v-cy-name="'task-add-title'" class="pa2 db w-100" type="text"
|
||||
@keyup.esc="addMode = false"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt3">
|
||||
<label for="add-description" class="db fw6 lh-copy f6">
|
||||
{{ $t('people.tasks_form_description') }}
|
||||
</label>
|
||||
<textarea id="add-description" v-model="newTask.description" class="pa2 db w-100" type="text" @keyup.esc="addMode = false"></textarea>
|
||||
</div>
|
||||
<div class="lh-copy mt3">
|
||||
<a v-cy-name="'save-task-button'" class="btn btn-primary" href="" @click.prevent="store">
|
||||
{{ $t('app.add') }}
|
||||
</a>
|
||||
<a class="btn" href="" @click.prevent="addMode = false">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- LIST OF COMPLETED TASKS -->
|
||||
<ul>
|
||||
<li v-for="task in completed(tasks)" :key="task.id" v-cy-name="'task-item-completed-' + task.id" class="f6">
|
||||
<form-checkbox
|
||||
v-model.lazy="task.completed"
|
||||
:disabled="task.disabled"
|
||||
:name="'checkbox'"
|
||||
:dclass="[ dirltr ? 'mr1' : 'ml1' ]"
|
||||
@change="toggleComplete(task)"
|
||||
>
|
||||
<span class="light-silver mr1">
|
||||
{{ task.completed_at }}
|
||||
</span>
|
||||
<span class="moon-gray" dir="auto">
|
||||
{{ task.title }}
|
||||
</span>
|
||||
<span v-if="task.description" class="silver ml3" dir="auto">
|
||||
{{ task.description }}
|
||||
</span>
|
||||
</form-checkbox>
|
||||
<div v-if="editMode" class="di">
|
||||
<em class="fa fa-trash-o pointer pr2 ml3 dark-blue" @click="trash(task)"></em>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
contactId: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
tasks: [],
|
||||
|
||||
updateMode: false,
|
||||
addMode: false,
|
||||
editMode: false,
|
||||
|
||||
newTask: {
|
||||
contact_id: 0,
|
||||
title: '',
|
||||
description: '',
|
||||
completed: 0
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.newTask.contact_id = this.contactId;
|
||||
this.index();
|
||||
},
|
||||
|
||||
methods: {
|
||||
reinitialize() {
|
||||
this.newTask.title = '';
|
||||
this.newTask.description = '';
|
||||
},
|
||||
|
||||
completed: function (tasks) {
|
||||
return tasks.filter(function (task) {
|
||||
return task.completed === true;
|
||||
});
|
||||
},
|
||||
|
||||
inProgress: function (tasks) {
|
||||
return tasks.filter(function (task) {
|
||||
return task.completed === false;
|
||||
});
|
||||
},
|
||||
|
||||
toggleAddMode() {
|
||||
this.addMode = true;
|
||||
this.reinitialize();
|
||||
},
|
||||
|
||||
toggleEditMode(task) {
|
||||
Vue.set(task, 'edit', !task.edit);
|
||||
},
|
||||
|
||||
index() {
|
||||
axios.get('people/' + this.hash + '/tasks')
|
||||
.then(response => {
|
||||
this.tasks = _.map(response.data, function (task) {
|
||||
return _.assign({}, task, {disabled: false});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
store() {
|
||||
axios.post('tasks', this.newTask)
|
||||
.then(response => {
|
||||
this.addMode = false;
|
||||
this.reinitialize();
|
||||
this.tasks.push(response.data);
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('app.default_save_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
toggleComplete(task) {
|
||||
this.updateMode = true;
|
||||
Vue.set(task, 'disabled', true);
|
||||
this.update(task, false);
|
||||
},
|
||||
|
||||
update(task, toggleEdit) {
|
||||
axios.put('tasks/' + task.id, task)
|
||||
.then(response => {
|
||||
this.updateMode = false;
|
||||
Vue.set(task, 'disabled', false);
|
||||
Vue.set(task, 'completed_at', response.data.completed_at ? this.formatDate(response.data.completed_at): null);
|
||||
if (toggleEdit) {
|
||||
this.toggleEditMode(task);
|
||||
}
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('app.default_save_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
formatDate(dateAsString) {
|
||||
const moment = require('moment-timezone');
|
||||
moment.locale(this._i18n.locale);
|
||||
moment.tz.setDefault('UTC');
|
||||
|
||||
var date = moment.tz(moment(dateAsString), this.$root.timezone);
|
||||
|
||||
return date.format('ll');
|
||||
},
|
||||
|
||||
trash(task) {
|
||||
axios.delete('tasks/' + task.id)
|
||||
.then(response => {
|
||||
this.tasks.splice(this.tasks.indexOf(task), 1);
|
||||
});
|
||||
|
||||
if (this.tasks.length <= 1) {
|
||||
this.editMode = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
237
resources/js/components/people/activity/ActivityList.vue
Normal file
237
resources/js/components/people/activity/ActivityList.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<style scoped>
|
||||
.btn-title {
|
||||
top: -7px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="">
|
||||
<h3 class="mb2">
|
||||
🍿 {{ $t('people.activity_title') }}
|
||||
|
||||
<span class="fr relative btn-title">
|
||||
<a v-if="displayLogActivity === false" v-cy-name="'add-activity-button'" class="btn edit-information" @click="displayLogActivity = true">
|
||||
{{ $t('people.activities_add_activity') }}
|
||||
</a>
|
||||
<a v-else class="btn edit-information" @click="displayLogActivity = false">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<!-- BLANK STATE -->
|
||||
<div v-if="!displayLogActivity && activities.length === 0" class="w-100">
|
||||
<div v-cy-name="'activities-blank-state'" class="bg-near-white tc pa3 br2 ba b--light-gray">
|
||||
<p>{{ $t('people.activities_blank_title', { name: name }) }}</p>
|
||||
<a class="pointer" href="" @click.prevent="displayLogActivity = true">
|
||||
{{ $t('people.activities_blank_add_activity') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LOG AN ACTIVITY -->
|
||||
<template v-if="displayLogActivity">
|
||||
<create-activity
|
||||
:hash="hash"
|
||||
:contact-id="contactId"
|
||||
:name="name"
|
||||
@update="updateList($event)"
|
||||
@cancel="displayLogActivity = false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- LIST OF ACTIVITIES -->
|
||||
<div v-cy-name="'activities-body'" v-cy-items="activities.map(c => c.id)">
|
||||
<div v-for="activity in activities" :key="activity.id" v-cy-name="'activity-body-'+activity.id" class="ba br2 b--black-10 br--top w-100 mb2">
|
||||
<template v-if="!activity.edit">
|
||||
<h2 class="pl2 pr2 pt3 f5">
|
||||
{{ activity.summary }}
|
||||
</h2>
|
||||
|
||||
<div v-if="activity.description" dir="auto" class="markdown pl2 pr2 pb3" v-html="compiledMarkdown(activity.description)">
|
||||
</div>
|
||||
|
||||
<!-- DETAILS -->
|
||||
<div class="pa2 cf bt b--black-10 br--bottom f7">
|
||||
<div class="w-70" :class="[ dirltr ? 'fl' : 'fr' ]">
|
||||
<ul class="list">
|
||||
<!-- HAPPENED AT -->
|
||||
<li class="di" :class="[ dirltr ? 'mr3' : 'ml3' ]">
|
||||
{{ activity.happened_at | moment }}
|
||||
</li>
|
||||
|
||||
<!-- PARTICIPANT LIST -->
|
||||
<li v-if="activity.attendees.total > 1" class="di">
|
||||
<ul class="di list" :class="[ dirltr ? 'mr3' : 'ml3' ]">
|
||||
<li class="di">
|
||||
{{ $t('people.activities_list_participants', { total: activity.attendees.total - 1}) }}
|
||||
</li>
|
||||
<li v-for="attendee in activity.attendees.contacts.filter(c => c.id !== contactId)" :key="attendee.id" class="di mr2">
|
||||
<a :href="'people/' + attendee.hash_id">{{ attendee.complete_name }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- EMOTIONS LIST -->
|
||||
<li v-if="activity.emotions.length !== 0" class="di">
|
||||
<ul class="di list" :class="[ dirltr ? 'mr3' : 'ml3' ]">
|
||||
<li class="di">
|
||||
{{ $t('people.activities_list_emotions') }}
|
||||
</li>
|
||||
<li v-for="emotion in activity.emotions" :key="emotion.id" class="di">
|
||||
{{ $t('app.emotion_' + emotion.name) }}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- ACTIVITY TYPE -->
|
||||
<li v-if="activity.activity_type" class="di" :class="[ dirltr ? 'mr3' : 'ml3' ]">
|
||||
{{ activity.activity_type.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="w-30" :class="[ dirltr ? 'fl tr' : 'fr tl' ]">
|
||||
<!-- ACTIONS -->
|
||||
<ul class="list">
|
||||
<li class="di">
|
||||
<a v-cy-name="'edit-activity-button-'+activity.id" href="" class="pointer" @click.prevent="$set(activity, 'edit', true)">{{ $t('app.edit') }}</a>
|
||||
<a v-show="destroyActivityId !== activity.id" v-cy-name="'delete-activity-button-'+activity.id" href="" class="pointer" @click.prevent="showDestroyActivity(activity)">{{ $t('app.delete') }}</a>
|
||||
<ul v-show="destroyActivityId === activity.id" class="di">
|
||||
<li class="di">
|
||||
<a v-cy-name="'confirm-delete-activity'" class="pointer red" @click.prevent="destroyActivity(activity)">
|
||||
{{ $t('app.delete_confirm') }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="di">
|
||||
<a class="pointer mr1" @click.prevent="destroyActivityId = 0">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- EDIT THE CURRENT ACTIVITY -->
|
||||
<create-activity v-else
|
||||
:hash="hash"
|
||||
:name="name"
|
||||
:activity="activity"
|
||||
:contact-id="contactId"
|
||||
@update="$set(activity, 'edit', false); updateList($event)"
|
||||
@cancel="$set(activity, 'edit', false); displayLogActivity = false"
|
||||
/>
|
||||
</div>
|
||||
<a v-if="!isLastPage" class="pointer mr1" style="float: right" @click.prevent="getActivities">
|
||||
{{ $t('app.load_more') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p v-if="activities.length > 0" class="tc">
|
||||
📗 <a :href="'people/' + hash + '/activities/summary'">{{ $t('people.activities_view_activities_report') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import CreateActivity from './CreateActivity.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CreateActivity
|
||||
},
|
||||
|
||||
filters: {
|
||||
moment: function (date) {
|
||||
return moment.utc(date).format('LL');
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
contactId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
displayLogActivity: false,
|
||||
activities: [],
|
||||
emotions: [],
|
||||
displayDescription: false,
|
||||
displayEmotions: false,
|
||||
displayCategory: false,
|
||||
displayParticipants: false,
|
||||
destroyActivityId: 0,
|
||||
errors: [],
|
||||
currentPage: 0,
|
||||
lastPage: null
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
},
|
||||
isLastPage () {
|
||||
return this.lastPage === this.currentPage;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getActivities();
|
||||
this.todayDate = moment().format('YYYY-MM-DD');
|
||||
},
|
||||
|
||||
compiledMarkdown (text) {
|
||||
return text !== undefined && text !== null ? marked(text, { sanitize: true }) : '';
|
||||
},
|
||||
|
||||
getActivities() {
|
||||
this.currentPage++;
|
||||
axios.get('api/contacts/' + this.contactId + '/activities?page=' + this.currentPage)
|
||||
.then(response => {
|
||||
this.activities.push(...response.data.data);
|
||||
this.currentPage = response.data.meta.current_page;
|
||||
this.lastPage = response.data.meta.last_page;
|
||||
});
|
||||
},
|
||||
|
||||
updateList(activity) {
|
||||
this.displayLogActivity = false;
|
||||
const index = this.activities.indexOf(this.activities.find(item => item.id === activity.id));
|
||||
Vue.set(this.activities, index >= 0 ? index : this.activities.length, activity);
|
||||
},
|
||||
|
||||
showDestroyActivity(activity) {
|
||||
this.destroyActivityId = activity.id;
|
||||
},
|
||||
|
||||
destroyActivity(activity) {
|
||||
axios.delete(`activities/${activity.id}`)
|
||||
.then(response => {
|
||||
this.activities.splice(this.activities.indexOf(activity), 1);
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
58
resources/js/components/people/activity/ActivityTypeList.vue
Normal file
58
resources/js/components/people/activity/ActivityTypeList.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<form-select
|
||||
:id="'activity-type-list'"
|
||||
v-model="choosenCategory"
|
||||
:title="title"
|
||||
:options="activityCategories"
|
||||
:iclass="'br2 f5 w-100 ba b--black-40 pa2 outline-0'"
|
||||
@input="$emit('input', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
choosenCategory: '',
|
||||
activityCategories: null,
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(val) {
|
||||
this.choosenCategory = val;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getActivities().then(() => {
|
||||
this.choosenCategory = this.value;
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
getActivities() {
|
||||
return axios.get('activityCategories')
|
||||
.then(response => {
|
||||
this.activityCategories = Object.assign({}, _.map(response.data, a => {
|
||||
return {
|
||||
name: a.name,
|
||||
options: a.types,
|
||||
};
|
||||
}));
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
297
resources/js/components/people/activity/CreateActivity.vue
Normal file
297
resources/js/components/people/activity/CreateActivity.vue
Normal file
@@ -0,0 +1,297 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- LOG AN ACTIVITY -->
|
||||
<transition name="fade">
|
||||
<div class="ba br3 mb3 pa3 b--black-40">
|
||||
<div class="dt dt--fixed pb3 mb3 mb0-ns bb b--gray-monica">
|
||||
<!-- SUMMARY -->
|
||||
<div class="dtc pr2">
|
||||
<form-input
|
||||
:id="'summary'"
|
||||
v-model="newActivity.summary"
|
||||
:input-type="'text'"
|
||||
:title="$t('people.activities_add_title', { name: name })"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- WHEN -->
|
||||
<div class="dtc">
|
||||
<p class="mb2 b">
|
||||
{{ $t('people.activities_add_date_occured') }}
|
||||
</p>
|
||||
<div class="di">
|
||||
<div class="dib">
|
||||
<form-date
|
||||
ref="date"
|
||||
v-model="newActivity.happened_at"
|
||||
:show-calendar-on-focus="true"
|
||||
:default-date="todayDate"
|
||||
:locale="locale"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ADDITIONAL FIELDS -->
|
||||
<div v-show="!displayDescription || !displayEmotions || !displayCategory || !displayParticipants" class="bb b--gray-monica pv3 mb3">
|
||||
<ul class="list">
|
||||
<li v-show="!displayDescription" class="di pointer mr3 nowrap-link">
|
||||
<a href="" @click.prevent="displayDescription = true">{{ $t('people.activities_add_more_details') }}</a>
|
||||
</li>
|
||||
<li v-show="!displayEmotions" class="di pointer mr3 nowrap-link">
|
||||
<a href="" @click.prevent="displayEmotions = true">{{ $t('people.activities_add_emotions') }}</a>
|
||||
</li>
|
||||
<li v-show="!displayCategory" class="di pointer mr3 nowrap-link">
|
||||
<a v-cy-name="'activities_add_category'" href="" @click.prevent="displayCategory = true">{{ $t('people.activities_add_category') }}</a>
|
||||
</li>
|
||||
<li v-show="!displayParticipants" class="di pointer nowrap-link">
|
||||
<a href="" @click.prevent="displayParticipants = true">{{ $t('people.activities_add_participants_cta') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- DESCRIPTION -->
|
||||
<div v-if="displayDescription" class="bb b--gray-monica pv3 mb3">
|
||||
<form-textarea
|
||||
v-model="newActivity.description"
|
||||
:required="true"
|
||||
:no-label="true"
|
||||
:rows="4"
|
||||
:title="$t('people.activities_summary')"
|
||||
:placeholder="$t('people.conversation_add_content')"
|
||||
@contentChange="updateDescription($event)"
|
||||
/>
|
||||
<p class="f6">
|
||||
{{ $t('app.markdown_description') }} <a href="https://guides.github.com/features/mastering-markdown/" rel="noopener noreferrer" target="_blank">
|
||||
{{ $t('app.markdown_link') }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- EMOTIONS -->
|
||||
<div v-if="displayEmotions" class="bb b--gray-monica pb3 mb3">
|
||||
<label>
|
||||
{{ $t('people.activities_add_emotions_title') }}
|
||||
</label>
|
||||
<emotion
|
||||
class="pv2"
|
||||
:initial-emotions="initialEmotions"
|
||||
@update="updateEmotionsList"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- ACTIVITY CATEGORIES -->
|
||||
<div v-if="displayCategory" class="bb b--gray-monica pb3 mb3">
|
||||
<activity-type-list
|
||||
v-model="newActivity.activity_type_id"
|
||||
:title="$t('people.activities_add_pick_activity')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- PARTICPANTS -->
|
||||
<div v-if="displayParticipants" class="bb b--gray-monica pb3 mb3">
|
||||
<label>
|
||||
{{ $t('people.activities_add_participants', {name: name}) }}
|
||||
</label>
|
||||
<participant
|
||||
:hash="hash"
|
||||
:initial-participants="participants"
|
||||
@update="updateParticipant($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<error :errors="errors" />
|
||||
|
||||
<!-- ACTIONS -->
|
||||
<div class="pt3">
|
||||
<div class="flex-ns justify-between">
|
||||
<div class="">
|
||||
<a class="btn btn-secondary tc w-auto-ns w-100 mb2 pb0-ns" @click.prevent="close()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="">
|
||||
<button v-cy-name="'save-activity-button'" class="btn btn-primary w-auto-ns w-100 mb2 pb0-ns" @click.prevent="store()">
|
||||
{{ activity ? $t('app.save') : $t('app.add') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import ActivityTypeList from './ActivityTypeList.vue';
|
||||
import Emotion from '../Emotion.vue';
|
||||
import Error from '../../partials/Error.vue';
|
||||
import Participant from '../Participant.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ActivityTypeList,
|
||||
Emotion,
|
||||
Error,
|
||||
Participant,
|
||||
},
|
||||
|
||||
filters: {
|
||||
moment: function (date) {
|
||||
return moment.utc(date).format('LL');
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
contactId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
activity: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
displayDescription: false,
|
||||
displayEmotions: false,
|
||||
displayCategory: false,
|
||||
displayParticipants: false,
|
||||
newActivity: {
|
||||
summary: '',
|
||||
description: '',
|
||||
happened_at: '',
|
||||
emotions: [],
|
||||
activity_type_id: null,
|
||||
contacts: [],
|
||||
},
|
||||
todayDate: '',
|
||||
initialEmotions: [],
|
||||
participants: [],
|
||||
errors: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
locale() {
|
||||
return this.$root.locale;
|
||||
},
|
||||
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
participants(value) {
|
||||
this.newActivity.contacts = _.map(value, p => p.id);
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.todayDate = moment().format('YYYY-MM-DD');
|
||||
this.resetFields();
|
||||
},
|
||||
|
||||
updateDescription(updatedDescription) {
|
||||
this.newActivity.description = updatedDescription;
|
||||
},
|
||||
|
||||
resetFields() {
|
||||
if (this.activity) {
|
||||
this.initialEmotions = JSON.parse(JSON.stringify(this.activity.emotions));
|
||||
this.newActivity.summary = this.activity.summary;
|
||||
this.newActivity.description = this.activity.description;
|
||||
this.newActivity.happened_at = this.activity.happened_at;
|
||||
this.updateEmotionsList(this.activity.emotions);
|
||||
this.newActivity.activity_type_id = this.activity.activity_type ? this.activity.activity_type.id : null;
|
||||
this.participants = this.activity.attendees.contacts.map(attendee => {
|
||||
return {
|
||||
id: attendee.id,
|
||||
name: attendee.complete_name,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
this.initialEmotions = [];
|
||||
this.newActivity.summary = '';
|
||||
this.newActivity.description = '';
|
||||
this.newActivity.happened_at = this.todayDate;
|
||||
this.newActivity.emotions = [];
|
||||
this.newActivity.activity_type_id = null;
|
||||
this.participants = [];
|
||||
}
|
||||
this.displayDescription = this.newActivity.description ? this.newActivity.description !== '' : false;
|
||||
this.displayEmotions = this.newActivity.emotions && this.newActivity.emotions.length > 0;
|
||||
this.displayCategory = this.newActivity.activity_type_id !== null;
|
||||
this.displayParticipants = this.participants.length > 0;
|
||||
this.errors = [];
|
||||
},
|
||||
|
||||
close() {
|
||||
this.resetFields();
|
||||
this.$emit('cancel');
|
||||
},
|
||||
|
||||
store() {
|
||||
const method = this.activity ? 'put' : 'post';
|
||||
const url = this.activity ? 'activities/'+this.activity.id : 'activities';
|
||||
|
||||
if (! this.newActivity.contacts.includes(this.contactId)) {
|
||||
this.newActivity.contacts.push(this.contactId);
|
||||
}
|
||||
|
||||
axios[method](url, this.newActivity)
|
||||
.then(response => {
|
||||
this.resetFields();
|
||||
this.$emit('update', response.data.data);
|
||||
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('people.activities_add_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this._errorHandle(error);
|
||||
});
|
||||
},
|
||||
|
||||
updateParticipant: function (participants) {
|
||||
this.participants = participants;
|
||||
},
|
||||
|
||||
updateEmotionsList: function(emotions) {
|
||||
// filter the list of emotions to populate a new array
|
||||
// containing only the emotion ids and not the entire objetcs
|
||||
this.newActivity.emotions = _.map(emotions, emotion => emotion.id);
|
||||
},
|
||||
|
||||
_errorHandle(error) {
|
||||
if (error.response && typeof error.response.data === 'object') {
|
||||
this.errors = _.flatten(_.toArray(error.response.data));
|
||||
} else {
|
||||
this.errors = [this.$t('app.error_try_again'), error.message];
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
47
resources/js/components/people/calls/LastCalled.vue
Normal file
47
resources/js/components/people/calls/LastCalled.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<span v-cy-name="'last-talked-to'">{{ lastCalledMessage }}</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
initialValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
lastCalled: '',
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
lastCalledMessage() {
|
||||
if (!this.initialValue && !this.lastCalled) {
|
||||
return this.$t('people.last_called_empty');
|
||||
}
|
||||
|
||||
if (!this.lastCalled) {
|
||||
return this.$t('people.last_talked_to', {date: this.initialValue});
|
||||
}
|
||||
|
||||
return this.$t('people.last_talked_to', {date: this.lastCalled});
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getLastCalled() {
|
||||
axios.get('people/' + this.hash + '/calls/last')
|
||||
.then(response => {
|
||||
this.lastCalled = response.data.last_talked_to;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
417
resources/js/components/people/calls/PhoneCallList.vue
Normal file
417
resources/js/components/people/calls/PhoneCallList.vue
Normal file
@@ -0,0 +1,417 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="">
|
||||
<h3 class="mb2">
|
||||
☎️ {{ $t('people.call_title') }}
|
||||
|
||||
<span class="fr relative" style="top: -7px;">
|
||||
<a v-if="displayLogCall === false" v-cy-name="'add-call-button'" class="btn edit-information" href="" @click.prevent="displayLogCall = true">
|
||||
{{ $t('people.call_button') }}
|
||||
</a>
|
||||
<a v-if="displayLogCall" class="btn edit-information" href="" @click.prevent="displayLogCall = false">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<!-- BLANK STATE -->
|
||||
<div v-if="!displayLogCall && calls.length === 0" v-cy-name="'calls-blank-state'" class="w-100">
|
||||
<div class="bg-near-white tc pa3 br2 ba b--light-gray">
|
||||
<p>{{ $t('people.call_blank_title', { name: name }) }}</p>
|
||||
<a class="pointer" href="" @click.prevent="displayLogCall = true">
|
||||
{{ $t('people.call_button') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LOG A CALL -->
|
||||
<transition name="fade">
|
||||
<div v-if="displayLogCall" v-cy-name="'log-call-form'" class="ba br3 mb3 pa3 b--black-40">
|
||||
<div class="dt dt--fixed pb3 mb3 mb0-ns">
|
||||
<!-- WHEN -->
|
||||
<div class="dtc pr2">
|
||||
<p class="mb2 b">
|
||||
{{ $t('people.modal_call_exact_date') }}
|
||||
</p>
|
||||
<div class="di mr3">
|
||||
<div class="dib">
|
||||
<form-date
|
||||
v-model="newCall.called_at"
|
||||
:default-date="todayDate"
|
||||
:locale="locale"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- WHO CALLED -->
|
||||
<div class="dtc">
|
||||
<p class="mb2 b">
|
||||
{{ $t('people.modal_call_who_called') }}
|
||||
</p>
|
||||
<div class="dt">
|
||||
<div class="dt-row">
|
||||
<form-radio
|
||||
v-model="newCall.contact_called"
|
||||
:name="'contact_called'"
|
||||
:value="false"
|
||||
:iclass="'mr1'"
|
||||
:dclass="'dtc mr3'"
|
||||
>
|
||||
{{ $t('people.call_you_called') }}
|
||||
</form-radio>
|
||||
<form-radio
|
||||
v-model="newCall.contact_called"
|
||||
:name="'contact_called'"
|
||||
:value="true"
|
||||
:iclass="'mr1'"
|
||||
:dclass="'dtc mr3'"
|
||||
>
|
||||
{{ $t('people.call_he_called', { name : name }) }}
|
||||
</form-radio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONTENT -->
|
||||
<div>
|
||||
<form-textarea
|
||||
v-model="newCall.content"
|
||||
:required="true"
|
||||
:label="$t('people.modal_call_comment')"
|
||||
:rows="4"
|
||||
:placeholder="$t('people.conversation_add_content')"
|
||||
/>
|
||||
<p class="f6">
|
||||
{{ $t('app.markdown_description') }} <a href="https://guides.github.com/features/mastering-markdown/" target="_blank" rel="noopener noreferrer">
|
||||
{{ $t('app.markdown_link') }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- EMOTIONS -->
|
||||
<div class="bb b--gray-monica pb3">
|
||||
<label class="b">
|
||||
{{ $t('people.modal_call_emotion') }}
|
||||
</label>
|
||||
<emotion class="pv2" @update="updateEmotionsList" />
|
||||
</div>
|
||||
|
||||
<!-- ACTIONS -->
|
||||
<div class="pt3">
|
||||
<div class="flex-ns justify-between">
|
||||
<div class="">
|
||||
<a class="btn btn-secondary tc w-auto-ns w-100 mb2 pb0-ns" href="" @click.prevent="displayLogCall = false; resetFields()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="">
|
||||
<button v-cy-name="'save-call-button'" class="btn btn-primary w-auto-ns w-100 mb2 pb0-ns" @click.prevent="store()">
|
||||
{{ $t('app.add') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- LIST OF CALLS -->
|
||||
<div v-cy-name="'calls-body'" v-cy-items="calls.map(c => c.id)">
|
||||
<div v-for="call in calls" :key="call.id" v-cy-name="'call-body-'+call.id" class="ba br2 b--black-10 br--top w-100 mb2">
|
||||
<div v-show="editCallId !== call.id" class="pa2">
|
||||
<span v-if="!call.content">
|
||||
{{ $t('people.call_blank_desc', { name: call.contact.first_name }) }}
|
||||
</span>
|
||||
<span v-if="call.content" dir="auto" class="markdown" v-html="compiledMarkdown(call.content)"></span>
|
||||
</div>
|
||||
|
||||
<!-- INLINE UPDATE DIV -->
|
||||
<div v-show="editCallId === call.id" class="pa2">
|
||||
<div>
|
||||
<form-textarea
|
||||
v-model="editCall.content"
|
||||
:label="$t('people.modal_call_comment')"
|
||||
:rows="4"
|
||||
iclass="br2 f5 w-100 ba b--black-40 pa2 outline-0"
|
||||
@contentChange="updateEditCallContent($event)"
|
||||
/>
|
||||
<p class="f6">
|
||||
{{ $t('app.markdown_description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- WHO CALLED -->
|
||||
<div class="pb3 mb3 mb0-ns">
|
||||
<p class="mb2">
|
||||
{{ $t('people.modal_call_who_called') }}
|
||||
</p>
|
||||
<div class="di mr3">
|
||||
<input :id="'you' + call.id" v-model="editCall.contact_called" type="radio" class="mr1" :name="'contact_called' + call.id"
|
||||
:value="false"
|
||||
/>
|
||||
<p class="f6">
|
||||
{{ $t('app.markdown_description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- WHO CALLED -->
|
||||
<div class="pb3 mb3 mb0-ns">
|
||||
<p class="mb2">
|
||||
{{ $t('people.modal_call_who_called') }}
|
||||
</p>
|
||||
<div class="di mr3">
|
||||
<input :id="'you' + call.id" v-model="editCall.contact_called" type="radio" class="mr1" :name="'contact_called' + call.id"
|
||||
:value="false"
|
||||
/>
|
||||
<label :for="'you' + call.id" class="pointer">
|
||||
{{ $t('people.call_you_called') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="di mr3">
|
||||
<input :id="'contact' + call.id" v-model="editCall.contact_called" type="radio" class="mr1" :name="'contact_called' + call.id"
|
||||
:value="true"
|
||||
/>
|
||||
<label :for="'contact' + call.id" class="pointer">
|
||||
{{ $t('people.call_he_called', { name : name }) }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EMOTIONS -->
|
||||
<div class="bb b--gray-monica pb3 mb3">
|
||||
<label class="b">
|
||||
{{ $t('people.modal_call_emotion') }}
|
||||
</label>
|
||||
<emotion class="pv2" :initial-emotions="call.emotions" @update="updateEmotionsList" />
|
||||
</div>
|
||||
|
||||
<!-- ACTIONS -->
|
||||
<div class="">
|
||||
<div class="flex-ns justify-between">
|
||||
<div class="">
|
||||
<a class="btn btn-secondary tc w-auto-ns w-100 mb2 pb0-ns" href="" @click.prevent="editCallId = 0">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="">
|
||||
<button class="btn btn-primary w-auto-ns w-100 mb2 pb0-ns" @click.prevent="update()">
|
||||
{{ $t('app.update') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ADDITIONAL INFORMATION -->
|
||||
<div class="pa2 cf bt b--black-10 br--bottom f7 lh-copy">
|
||||
<div class="w-70" :class="[ dirltr ? 'fl' : 'fr' ]">
|
||||
<span :class="[ dirltr ? 'mr3' : 'ml3' ]">
|
||||
{{ call.called_at | moment }}
|
||||
</span>
|
||||
<span :class="[ dirltr ? 'mr3' : 'ml3' ]">
|
||||
{{ call.contact_called ? $t('people.call_he_called', { name : name }) : $t('people.call_you_called') }}
|
||||
</span>
|
||||
|
||||
<!-- EMOTION LIST -->
|
||||
<span v-if="call.emotions.length !== 0">
|
||||
<span :class="[ dirltr ? 'mr2' : 'ml2' ]">
|
||||
{{ $t('people.call_emotions') }}
|
||||
</span>
|
||||
<ul class="di">
|
||||
<li v-for="emotion in call.emotions" :key="emotion.id" class="di">
|
||||
{{ $t('app.emotion_' + emotion.name) }}
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div :class="[ dirltr ? 'fl tr' : 'fr tl' ]" class="w-30">
|
||||
<a :class="[ dirltr ? 'mr2' : 'ml2' ]" class="pointer " href="" @click.prevent="showEditBox(call)">
|
||||
{{ $t('app.update') }}
|
||||
</a>
|
||||
<a v-show="destroyCallId !== call.id" v-cy-name="'delete-call-button-'+call.id" class="pointer" href="" @click.prevent="showDestroyCall(call)">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
<ul v-show="destroyCallId === call.id" class="di">
|
||||
<li class="di">
|
||||
<a class="pointer mr1" href="" @click.prevent="destroyCallId = 0">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="di">
|
||||
<a v-cy-name="'delete-call-confirm-button-'+call.id" class="pointer red" href="" @click.prevent="destroyCall(call)">
|
||||
{{ $t('app.delete_confirm') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import Emotion from '../Emotion.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Emotion,
|
||||
},
|
||||
|
||||
filters: {
|
||||
moment: function (date) {
|
||||
return moment.utc(date).format('LL');
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
calls: [],
|
||||
displayLogCall: false,
|
||||
todayDate: '',
|
||||
editCallId: 0,
|
||||
destroyCallId: 0,
|
||||
chosenEmotions: [],
|
||||
newCall: {
|
||||
content: '',
|
||||
called_at: '',
|
||||
contact_called: false,
|
||||
emotions: [],
|
||||
},
|
||||
editCall: {
|
||||
content: '',
|
||||
contact_called: false,
|
||||
emotions: [],
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
},
|
||||
locale() {
|
||||
return this.$root.locale;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent(this.hash);
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent(hash) {
|
||||
this.getCalls();
|
||||
this.todayDate = moment().format('YYYY-MM-DD');
|
||||
this.newCall.called_at = this.todayDate;
|
||||
},
|
||||
|
||||
compiledMarkdown (text) {
|
||||
return text !== undefined && text !== null ? marked(text, { sanitize: true }) : '';
|
||||
},
|
||||
|
||||
resetFields() {
|
||||
this.newCall.content = '';
|
||||
this.newCall.called_at = this.todayDate;
|
||||
},
|
||||
|
||||
getCalls() {
|
||||
axios.get('people/' + this.hash + '/calls')
|
||||
.then(response => {
|
||||
this.calls = response.data.data;
|
||||
});
|
||||
},
|
||||
|
||||
store() {
|
||||
axios.post('people/' + this.hash + '/calls', this.newCall)
|
||||
.then(response => {
|
||||
this.getCalls();
|
||||
this.resetFields();
|
||||
this.displayLogCall = false;
|
||||
this.chosenEmotions = [];
|
||||
this.updateLastCalled();
|
||||
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('people.calls_add_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
update() {
|
||||
axios.put('people/' + this.hash + '/calls/' + this.editCallId, this.editCall)
|
||||
.then(response => {
|
||||
this.getCalls();
|
||||
this.editCallId = 0;
|
||||
this.chosenEmotions = [];
|
||||
this.updateLastCalled();
|
||||
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('app.default_save_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
updateLastCalled() {
|
||||
this.$parent.$refs.lastCalledAttribute.getLastCalled();
|
||||
},
|
||||
|
||||
showEditBox(call) {
|
||||
this.editCallId = call.id;
|
||||
this.editCall.content = call.content;
|
||||
this.editCall.contact_called = call.contact_called;
|
||||
this.editCall.called_at = moment.utc(call.called_at).format('YYYY-MM-DD');
|
||||
},
|
||||
|
||||
updateDate(updatedContent) {
|
||||
this.newCall.called_at = updatedContent;
|
||||
},
|
||||
|
||||
showDestroyCall(call) {
|
||||
this.destroyCallId = call.id;
|
||||
},
|
||||
|
||||
destroyCall(call) {
|
||||
axios.delete('people/' + this.hash + '/calls/' + this.destroyCallId)
|
||||
.then(response => {
|
||||
this.calls.splice(this.calls.indexOf(call), 1);
|
||||
this.updateLastCalled();
|
||||
});
|
||||
},
|
||||
|
||||
updateEmotionsList: function(emotions) {
|
||||
this.chosenEmotions = emotions;
|
||||
this.newCall.emotions = [];
|
||||
this.editCall.emotions = [];
|
||||
|
||||
// filter the list of emotions to populate a new array
|
||||
// containing only the emotion ids and not the entire objetcs
|
||||
for (let i = 0; i < this.chosenEmotions.length; i++) {
|
||||
this.newCall.emotions.push(this.chosenEmotions[i].id);
|
||||
this.editCall.emotions.push(this.chosenEmotions[i].id);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
114
resources/js/components/people/conversation/Conversation.vue
Normal file
114
resources/js/components/people/conversation/Conversation.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<style scoped>
|
||||
.me {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid transparent;
|
||||
border-left: 10px solid #fff;
|
||||
top: 120px;
|
||||
right: -10px;
|
||||
}
|
||||
|
||||
.other {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 10px solid transparent;
|
||||
border-bottom: 10px solid transparent;
|
||||
border-right:10px solid #fff;
|
||||
top: 120px;
|
||||
left: -10px;
|
||||
}
|
||||
|
||||
.conversation-block {
|
||||
background-color: #D8E2E7;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="pa4-ns ph3 pv2 mb3 mb0-ns bb b--gray-monica">
|
||||
<p class="mb2 b">
|
||||
{{ $t('people.conversation_add_what_was_said') }}
|
||||
</p>
|
||||
<div class="pa3 ba b--gray-monica br3 conversation-block">
|
||||
<div v-for="message in messages" :key="message.uid" class="relative">
|
||||
<div :class="message.author + ' absolute'"></div>
|
||||
<message
|
||||
v-model="message.content"
|
||||
:class="{ 'mb3 ml5': message.author === 'me', 'mb3 mr5': message.author === 'other' }"
|
||||
:author="message.author"
|
||||
:uid="message.uid"
|
||||
:participant-name="participantName"
|
||||
:display-trash="displayTrash"
|
||||
@updateAuthor="updateAuthor($event, message)"
|
||||
@deleteMessage="deleteMessage($event, message)"
|
||||
/>
|
||||
</div>
|
||||
<p class="tc mb0">
|
||||
<a class="btn btn-secondary pointer" href="" @click.prevent="addMessage">
|
||||
{{ $t('people.conversation_add_another') }}
|
||||
</a>
|
||||
</p>
|
||||
<input type="hidden" name="messages" :value="messages.map(a => a.uid)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
participantName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
existingMessages: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
messages: [],
|
||||
uid: 1,
|
||||
displayTrash: false,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.existingMessages.length > 0) {
|
||||
this.messages = this.existingMessages;
|
||||
this.uid = this.messages[this.messages.length - 1].uid + 1;
|
||||
} else {
|
||||
var me = this;
|
||||
setTimeout(function () {
|
||||
me.addMessage();
|
||||
}, 10);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
addMessage() {
|
||||
this.messages.push({
|
||||
uid: this.uid++,
|
||||
content: '',
|
||||
author: 'me'
|
||||
});
|
||||
if (this.messages.length > 1) {
|
||||
this.displayTrash = true;
|
||||
}
|
||||
},
|
||||
|
||||
updateAuthor(updatedAuthor, message) {
|
||||
message.author = updatedAuthor;
|
||||
},
|
||||
|
||||
deleteMessage(uid, message) {
|
||||
this.messages.splice(this.messages.indexOf(this.messages.find(item => item.uid === uid)), 1);
|
||||
if (this.messages.length <= 1) {
|
||||
this.displayTrash = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div>
|
||||
<vue-good-table
|
||||
:columns="columns"
|
||||
:rows="conversations"
|
||||
style-class="vgt-table"
|
||||
:sort-options="{
|
||||
enabled: false
|
||||
}"
|
||||
:pagination-options="{
|
||||
enabled: true
|
||||
}"
|
||||
@on-row-click="onRowClick"
|
||||
>
|
||||
<template slot="table-row" slot-scope="props">
|
||||
<span v-if="props.column.field === 'message_count'">
|
||||
<span>
|
||||
{{ props.row.message_count }}
|
||||
<svg width="18" height="15" viewBox="0 0 18 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.29412 14.6014V12.8182H1.69853C0.772058 12.8182 0 12.0839 0 11.2028V6.67133C0 5.79021 0.772058 5.05594 1.69853 5.05594H5.60294C5.82353 5.05594 6.02206 5.22378 6.02206 5.45455C6.02206 5.68531 5.84559 5.85315 5.60294 5.85315H1.69853C1.21323 5.85315 0.816176 6.23077 0.816176 6.69231V11.2238C0.816176 11.6853 1.21323 12.0629 1.69853 12.0629H2.71324C2.93382 12.0629 3.13235 12.2308 3.13235 12.4615V13.6993L4.80882 12.1678C4.875 12.1049 4.9853 12.0629 5.09559 12.0629H9.52941C10.0147 12.0629 10.4118 11.6853 10.4118 11.2238V8.91608C10.4118 8.70629 10.5882 8.51748 10.8309 8.51748C11.0515 8.51748 11.25 8.68532 11.25 8.91608V11.2238C11.25 12.1049 10.4779 12.8392 9.55147 12.8392H5.25L3 14.8951C2.91177 14.958 2.82353 15 2.71324 15C2.66912 15 2.60294 15 2.55882 14.958C2.40441 14.8951 2.29412 14.7482 2.29412 14.6014ZM12.7059 7.84615H8.40441C7.45588 7.84615 6.68382 7.11188 6.68382 6.20979V1.63636C6.68382 0.734263 7.45588 0 8.40441 0H16.2794C17.2279 0 18 0.734263 18 1.63636V6.20979C18 7.11188 17.2279 7.84615 16.2794 7.84615H15.6838V9.62937C15.6838 9.7972 15.5956 9.92307 15.4412 9.98601C15.3971 10.007 15.3309 10.028 15.2868 10.028C15.1765 10.028 15.0882 9.98601 15 9.92307L12.7059 7.84615ZM8.40441 7.06993H12.8603C12.9706 7.06993 13.0588 7.11188 13.1471 7.17482L14.8456 8.72727V7.46853C14.8456 7.25874 15.0221 7.06993 15.2647 7.06993H16.2794C16.7647 7.06993 17.1618 6.6923 17.1618 6.23077V1.65734C17.1618 1.1958 16.7647 0.818181 16.2794 0.818181H8.40441C7.91912 0.818181 7.52206 1.1958 7.52206 1.65734V6.23077C7.52206 6.6923 7.91912 7.06993 8.40441 7.06993ZM12.3529 3.54545C12.0441 3.54545 11.7794 3.7972 11.7794 4.09091C11.7794 4.38462 12.0441 4.63636 12.3529 4.63636C12.6618 4.63636 12.9265 4.38462 12.9265 4.09091C12.9265 3.7972 12.6618 3.54545 12.3529 3.54545ZM14.4926 4.63636C14.8015 4.63636 15.0662 4.38462 15.0662 4.09091C15.0662 3.7972 14.8015 3.54545 14.4926 3.54545C14.1838 3.54545 13.9191 3.7972 13.9191 4.09091C13.9191 4.38462 14.1838 4.63636 14.4926 4.63636ZM10.1912 3.54545C9.88235 3.54545 9.61765 3.7972 9.61765 4.09091C9.61765 4.38462 9.88235 4.63636 10.1912 4.63636C10.5 4.63636 10.7647 4.38462 10.7647 4.09091C10.7647 3.7972 10.5 3.54545 10.1912 3.54545Z" fill="#67718A" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ props.formattedRow[props.column.field] }}
|
||||
</span>
|
||||
</template>
|
||||
</vue-good-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { VueGoodTable } from 'vue-good-table';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
VueGoodTable
|
||||
},
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
conversations: [],
|
||||
|
||||
columns: [
|
||||
{
|
||||
label: this.$t('app.date'),
|
||||
field: 'happened_at',
|
||||
tdClass: 'vgt-table-date',
|
||||
},
|
||||
{
|
||||
label: this.$t('app.type'),
|
||||
field: 'contact_field_type',
|
||||
width: '110px',
|
||||
},
|
||||
{
|
||||
label: this.$t('people.conversation_list_table_messages'),
|
||||
field: 'message_count',
|
||||
width: '110px',
|
||||
},
|
||||
{
|
||||
label: this.$t('people.conversation_list_table_content'),
|
||||
field: 'content',
|
||||
}
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent(this.hash);
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent(hash) {
|
||||
axios.get('people/' + hash + '/conversations')
|
||||
.then(response => {
|
||||
this.conversations = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
onRowClick(params) {
|
||||
window.location.href = params.row.route;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
139
resources/js/components/people/conversation/Message.vue
Normal file
139
resources/js/components/people/conversation/Message.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<style scoped>
|
||||
.delete-message-l {
|
||||
top: 9px;
|
||||
right: 16px;
|
||||
}
|
||||
|
||||
.delete-message-r {
|
||||
top: 9px;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
font-size: 12px;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="bg-white pa3 relative br3">
|
||||
<!-- TRASH CAN -->
|
||||
<span v-if="displayTrash" :class="[dirltr ? 'absolute delete-message-l' : 'absolute delete-message-r']">
|
||||
<a class="pointer btn btn-secondary" href="" @click.prevent="deleteMessage">
|
||||
<svg class="mr1 relative" style="top: 1px;" width="9" height="11" viewBox="0 0 9 11"
|
||||
fill="none" xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.27273 0.714286H5.81818C5.81818 0.321429 5.49091 0 5.09091 0H2.90909C2.50909 0 2.18182 0.321429 2.18182 0.714286H0.727273C0.327273 0.714286 0 1.03571 0 1.42857V2.14286C0 2.53571 0.327273 2.85714 0.727273 2.85714V9.28571C0.727273 9.67857 1.05455 10 1.45455 10H6.54545C6.94545 10 7.27273 9.67857 7.27273 9.28571V2.85714C7.67273 2.85714 8 2.53571 8 2.14286V1.42857C8 1.03571 7.67273 0.714286 7.27273 0.714286ZM6.54545 9.28571H1.45455V2.85714H2.18182V8.57143H2.90909V2.85714H3.63636V8.57143H4.36364V2.85714H5.09091V8.57143H5.81818V2.85714H6.54545V9.28571ZM7.27273 2.14286H0.727273V1.42857H7.27273V2.14286Z" fill="#626262" />
|
||||
</svg>
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<!-- AUTHOR -->
|
||||
<div class="mb3">
|
||||
<span class="di mr3">
|
||||
{{ $t('people.conversation_add_who_wrote') }}
|
||||
</span>
|
||||
<div class="di mr3">
|
||||
<input :id="'other_' + uid" v-model="updatedAuthor" class="pointer" type="radio" :name="'who_wrote_' + uid"
|
||||
value="other" :checked="updatedAuthor === 'other'" @click="updateAuthor('other')"
|
||||
/>
|
||||
<label :for="'other_' + uid" class="pointer">
|
||||
{{ participantName }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="di">
|
||||
<input :id="'me_' + uid" v-model="updatedAuthor" class="pointer" type="radio" :name="'who_wrote_' + uid"
|
||||
value="me" :checked="updatedAuthor === 'me'" @click="updateAuthor('me')"
|
||||
/>
|
||||
<label :for="'me_' + uid" class="pointer">
|
||||
{{ $t('people.conversation_add_you') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ACTUAL COMMENT -->
|
||||
<form-textarea
|
||||
:id="'content_' + uid"
|
||||
v-model="buffer"
|
||||
:required="true"
|
||||
:no-label="true"
|
||||
:rows="4"
|
||||
:placeholder="$t('people.conversation_add_content')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
uid: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
participantName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
author: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
displayTrash: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
buffer: '',
|
||||
updatedAuthor: this.author,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.buffer = newValue;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.buffer = this.value;
|
||||
},
|
||||
|
||||
methods: {
|
||||
deleteMessage() {
|
||||
this.$emit('deleteMessage', this.uid);
|
||||
},
|
||||
|
||||
updateAuthor(newAuthor) {
|
||||
this.updatedOther = newAuthor;
|
||||
this.$emit('updateAuthor', newAuthor);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
345
resources/js/components/people/document/DocumentList.vue
Normal file
345
resources/js/components/people/document/DocumentList.vue
Normal file
@@ -0,0 +1,345 @@
|
||||
<style scoped>
|
||||
.document-upload-zone {
|
||||
background: #fff;
|
||||
border: 1px solid #d6d6d6;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.document-type {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.document-type-icon {
|
||||
background: #ADF4FE;
|
||||
border-radius: 13px;
|
||||
font-size: 11px;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
.document-date {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.document-action {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.document-action-wrapper {
|
||||
background: #fff;
|
||||
opacity: 0.8;
|
||||
border-radius: 3px;
|
||||
line-height: 18px;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
.document-action-wrapper:hover {
|
||||
box-shadow: 1px 0px 1px rgba(43, 45, 80, 0.16), -1px 1px 1px rgba(43, 45, 80, 0.16), 0px 1px 4px rgba(43, 45, 80, 0.18);
|
||||
}
|
||||
|
||||
.document-action-menu {
|
||||
border-radius: 3px;
|
||||
box-shadow: 1px 0px 1px rgba(43, 45, 80, 0.16), -1px 1px 1px rgba(43, 45, 80, 0.16), 0px 1px 4px rgba(43, 45, 80, 0.18);
|
||||
right: 10px;
|
||||
top: 34px;
|
||||
}
|
||||
|
||||
.document-action-menu-item.delete {
|
||||
color: #CB4066;
|
||||
}
|
||||
|
||||
.document-action-menu-item.delete:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
progress {
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
height: 8px;
|
||||
margin-bottom: 3px;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
background: #e2e7ee;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 1px 1px 0 rgba(255, 255, 255, 0.4);
|
||||
background-color: #329FF1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="">
|
||||
<h3>
|
||||
📄 {{ $t('people.document_list_title') }}
|
||||
|
||||
<span v-show="reachLimit === 'false'" class="fr relative" style="top: -7px;">
|
||||
<a v-if="displayUploadZone === false && displayUploadError === false && displayUploadProgress === false" class="btn edit-information" html="" @click.prevent="displayUploadZone = true">
|
||||
{{ $t('people.document_list_cta') }}
|
||||
</a>
|
||||
<a v-if="displayUploadZone || displayUploadError || displayUploadProgress" class="btn edit-information" href="" @click.prevent="displayUploadZone = false; displayUploadError = false; displayUploadProgress = false">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<p v-show="reachLimit === 'true'">
|
||||
{{ $t('settings.storage_upgrade_notice') }}
|
||||
</p>
|
||||
|
||||
<!-- EMPTY STATE -->
|
||||
<div v-if="displayUploadZone === false && displayUploadError === false && displayUploadProgress === false && documents.length === 0" class="ltr w-100 pt2">
|
||||
<div class="section-blank">
|
||||
<h3 class="mb0">
|
||||
{{ $t('people.document_list_blank_desc') }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FIRST STEP OF DOCUMENT UPLOAD -->
|
||||
<transition name="fade">
|
||||
<div v-if="displayUploadZone" class="ba br3 document-upload-zone mb3 pa3">
|
||||
<div class="tc">
|
||||
<svg width="120" height="150" viewBox="0 0 120 150" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M27.2012 35.9138V101.562H92.7999V18.4375H44.6762L27.2012 35.9138ZM44.1274 21.6388V35.365H30.4024L44.1274 21.6388ZM46.0024 20.3125H90.9249V99.6875H29.0762V37.24H46.0024V20.3125Z" fill="#868E99" />
|
||||
<path d="M63.9413 42.7338H36.3013V44.6088H63.9413V42.7338Z" fill="#868E99" />
|
||||
<path d="M63.9413 52.6112H36.3013V54.4862H63.9413V52.6112Z" fill="#868E99" />
|
||||
<path d="M83.6988 62.4888H36.3013V64.3638H83.6988V62.4888Z" fill="#868E99" />
|
||||
<path d="M83.6988 72.3676H36.3013V74.2426H83.6988V72.3676Z" fill="#868E99" />
|
||||
<path d="M83.6988 82.245H36.3013V84.12H83.6988V82.245Z" fill="#868E99" />
|
||||
<path d="M83.6988 92.1226H36.3013V93.9976H83.6988V92.1226Z" fill="#868E99" />
|
||||
<path d="M84.6362 41.585H67.8774V58.3425H84.6362V41.585ZM82.7612 56.4675H69.7524V43.46H82.7612V56.4675Z" fill="#868E99" />
|
||||
<path d="M35.2864 107.656C33.6551 107.656 32.3276 108.984 32.3276 110.615C32.3276 112.246 33.6551 113.574 35.2864 113.574C36.9176 113.574 38.2451 112.246 38.2451 110.615C38.2451 108.984 36.9176 107.656 35.2864 107.656ZM35.2864 112.636C34.1714 112.636 33.2651 111.729 33.2651 110.615C33.2651 109.501 34.1726 108.594 35.2864 108.594C36.4001 108.594 37.3076 109.501 37.3076 110.615C37.3076 111.729 36.4001 112.636 35.2864 112.636Z" fill="#868E99" />
|
||||
<path d="M10.6401 41.265L13.3251 42.6762L12.8126 39.6862L14.9851 37.5687L11.9839 37.1325L10.6401 34.4125L9.29764 37.1325L6.29639 37.5687L8.46889 39.6862L7.95639 42.6762L10.6401 41.265ZM8.31014 38.2225L9.92014 37.9887L10.6401 36.53L11.3601 37.9887L12.9701 38.2225L11.8051 39.3587L12.0801 40.9625L10.6401 40.2062L9.20014 40.9625L9.47514 39.3587L8.31014 38.2225Z" fill="#868E99" />
|
||||
<path d="M36.3237 10.1125L35.365 8.17004L34.405 10.1125L32.2612 10.425L33.8125 11.9375L33.4462 14.0725L35.3637 13.065L37.2812 14.0725L36.9162 11.9375L38.4675 10.425L36.3237 10.1125ZM36.0375 12.36L35.365 12.0063L34.6925 12.36L34.8212 11.61L34.275 11.0788L35.0275 10.97L35.3637 10.2888L35.7 10.97L36.4525 11.0788L35.9075 11.61L36.0375 12.36Z" fill="#868E99" />
|
||||
<path d="M108.859 87.615L110.776 88.6225L110.41 86.4875L111.961 84.975L109.818 84.6625L108.859 82.72L107.9 84.6625L105.756 84.975L107.308 86.4875L106.941 88.6225L108.859 87.615ZM108.523 85.52L108.859 84.8387L109.195 85.52L109.948 85.6287L109.403 86.16L109.531 86.91L108.859 86.5562L108.186 86.91L108.315 86.16L107.77 85.6287L108.523 85.52Z" fill="#868E99" />
|
||||
<path d="M108.859 17.1625L107.618 20.9837H103.6L106.849 23.345L105.609 27.165L108.859 24.8037L112.109 27.165L110.868 23.345L114.116 20.9837H110.1L108.859 17.1625ZM109.765 22.9875L110.325 24.71L108.859 23.645L107.391 24.7112L107.951 22.9875L106.485 21.9212H108.299L108.859 20.1975L109.419 21.9212H111.233L109.765 22.9875Z" fill="#868E99" />
|
||||
<path d="M98.9138 6.42749C97.2826 6.42749 95.9551 7.75499 95.9551 9.38624C95.9551 11.0175 97.2826 12.345 98.9138 12.345C100.545 12.345 101.873 11.0175 101.873 9.38624C101.873 7.75499 100.545 6.42749 98.9138 6.42749ZM98.9138 11.4075C97.7988 11.4075 96.8926 10.5 96.8926 9.38624C96.8926 8.27249 97.8001 7.36499 98.9138 7.36499C100.028 7.36499 100.935 8.27249 100.935 9.38624C100.935 10.5 100.028 11.4075 98.9138 11.4075Z" fill="#868E99" />
|
||||
<path d="M110.779 58.8438C109.461 58.8438 108.39 59.915 108.39 61.2325C108.39 62.55 109.461 63.6213 110.779 63.6213C112.096 63.6213 113.168 62.55 113.168 61.2325C113.168 59.915 112.096 58.8438 110.779 58.8438ZM110.779 62.6837C109.979 62.6837 109.328 62.0325 109.328 61.2325C109.328 60.4325 109.979 59.7813 110.779 59.7813C111.579 59.7813 112.23 60.4325 112.23 61.2325C112.23 62.0325 111.579 62.6837 110.779 62.6837Z" fill="#868E99" />
|
||||
<path d="M18.6973 18.3362C16.9836 18.3362 15.5898 19.7299 15.5898 21.4437C15.5898 23.1574 16.9836 24.5512 18.6973 24.5512C20.4111 24.5512 21.8048 23.1574 21.8048 21.4437C21.8048 19.7299 20.4111 18.3362 18.6973 18.3362ZM18.6973 23.6137C17.5011 23.6137 16.5273 22.6399 16.5273 21.4437C16.5273 20.2474 17.5011 19.2737 18.6973 19.2737C19.8936 19.2737 20.8673 20.2474 20.8673 21.4437C20.8673 22.6399 19.8936 23.6137 18.6973 23.6137Z" fill="#868E99" />
|
||||
<path d="M27.4414 110.794C27.8502 110.898 28.2652 110.99 28.6839 111.074L28.8677 110.155C28.4639 110.074 28.0652 109.984 27.6702 109.885L27.4414 110.794Z" fill="#868E99" />
|
||||
<path d="M9.64111 94.6313C9.91986 95.4101 10.2424 96.1876 10.5999 96.9413L11.4474 96.5413C11.1036 95.8138 10.7936 95.0663 10.5249 94.3151L9.64111 94.6313Z" fill="#868E99" />
|
||||
<path d="M11.7773 99.1475C12.2048 99.86 12.6711 100.56 13.1661 101.229L13.9198 100.671C13.4448 100.028 12.9936 99.3525 12.5823 98.6663L11.7773 99.1475Z" fill="#868E99" />
|
||||
<path d="M22.7339 109.106C23.4851 109.463 24.2639 109.785 25.0464 110.066L25.3626 109.184C24.6076 108.914 23.8589 108.603 23.1351 108.26L22.7339 109.106Z" fill="#868E99" />
|
||||
<path d="M14.75 103.164C15.305 103.78 15.8988 104.378 16.5163 104.938L17.1475 104.244C16.5538 103.704 15.9813 103.129 15.4463 102.536L14.75 103.164Z" fill="#868E99" />
|
||||
<path d="M9.08887 86.0649C9.08887 85.6587 9.09887 85.2512 9.11887 84.8412L8.18262 84.7949C8.16137 85.2199 8.15137 85.6424 8.15137 86.0649C8.15137 86.4774 8.16137 86.8887 8.18137 87.2974L9.11762 87.2524C9.09887 86.8574 9.08887 86.4612 9.08887 86.0649Z" fill="#868E99" />
|
||||
<path d="M9.35279 89.6475L8.42529 89.785C8.54779 90.6062 8.71154 91.4312 8.91404 92.2375L9.82404 92.0087C9.62904 91.2325 9.47029 90.4387 9.35279 89.6475Z" fill="#868E99" />
|
||||
<path d="M18.4473 106.53C19.116 107.027 19.816 107.496 20.526 107.924L21.0098 107.12C20.326 106.709 19.6523 106.257 19.0073 105.777L18.4473 106.53Z" fill="#868E99" />
|
||||
<path d="M9.36012 82.425C9.42012 82.0225 9.49012 81.6187 9.57012 81.2162L8.65137 81.0325C8.56762 81.4512 8.49512 81.8687 8.43262 82.2875L9.36012 82.425Z" fill="#868E99" />
|
||||
<path d="M111.323 96.2138L112.257 96.2988C112.295 95.8763 112.319 95.4488 112.328 95.0176L111.39 94.9976C111.382 95.4076 111.359 95.8126 111.323 96.2138Z" fill="#868E99" />
|
||||
<path d="M110.009 101.347L110.869 101.721C111.241 100.865 111.546 99.9725 111.779 99.0675L110.87 98.8337C110.651 99.6912 110.361 100.537 110.009 101.347Z" fill="#868E99" />
|
||||
<path d="M107.156 105.81L107.858 106.433C108.48 105.733 109.049 104.98 109.55 104.196L108.76 103.691C108.284 104.434 107.745 105.146 107.156 105.81Z" fill="#868E99" />
|
||||
<path d="M98.1226 111.079L98.3176 111.995C99.2288 111.801 100.134 111.531 101.005 111.192L100.666 110.319C99.8413 110.64 98.9851 110.895 98.1226 111.079Z" fill="#868E99" />
|
||||
<path d="M103.059 109.166L103.531 109.975C104.337 109.505 105.111 108.966 105.834 108.374L105.239 107.649C104.555 108.211 103.821 108.721 103.059 109.166Z" fill="#868E99" />
|
||||
<path d="M94.2637 111.444L94.2437 112.381C94.3649 112.384 94.4862 112.385 94.6074 112.385C94.9162 112.385 95.2224 112.378 95.5287 112.361L95.4799 111.425C95.0774 111.446 94.6724 111.455 94.2637 111.444Z" fill="#868E99" />
|
||||
<path d="M111.248 45.4712H110.31V47.9712H111.248V45.4712Z" fill="#868E99" />
|
||||
<path d="M111.248 40.4712H110.31V42.9712H111.248V40.4712Z" fill="#868E99" />
|
||||
<path d="M111.248 35.4712H110.31V37.9712H111.248V35.4712Z" fill="#868E99" />
|
||||
<path d="M111.248 50.4712H110.31V52.9712H111.248V50.4712Z" fill="#868E99" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="tc dib w-100 relative">
|
||||
<button class="btn">
|
||||
{{ $t('people.document_upload_zone_cta') }}
|
||||
</button>
|
||||
<input id="file" ref="file" type="file" class="absolute o-0 w-100 h-100 pointer" style="left:0;"
|
||||
@change="handleFileUpload()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- LAST STEP OF DOCUMENT UPLOAD -->
|
||||
<div v-if="displayUploadProgress" class="ba br3 document-upload-zone mb3 pa3">
|
||||
<p class="tc mb1">
|
||||
{{ $t('people.document_upload_zone_progress') }}
|
||||
</p>
|
||||
<div class="tc mb1">
|
||||
<progress max="100" :value.prop="uploadPercentage"></progress>
|
||||
</div>
|
||||
<p class="tc f6 mb0">
|
||||
{{ uploadPercentage }}% uploaded
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- ERROR STEP WHEN UPLOADING A DOCUMENT -->
|
||||
<transition name="fade">
|
||||
<div v-if="displayUploadError" class="ba br3 document-upload-zone mb3 pa3">
|
||||
<div class="tc">
|
||||
<svg width="120" height="150" viewBox="0 0 120 150" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M27.2012 35.9138V101.562H92.7999V18.4375H44.6762L27.2012 35.9138ZM44.1274 21.6388V35.365H30.4024L44.1274 21.6388ZM46.0024 20.3125H90.9249V99.6875H29.0762V37.24H46.0024V20.3125Z" fill="#868E99" />
|
||||
<path d="M83.6988 72.3676H36.3013V74.2426H83.6988V72.3676Z" fill="#868E99" />
|
||||
<path d="M83.6988 82.245H36.3013V84.12H83.6988V82.245Z" fill="#868E99" />
|
||||
<path d="M83.6988 92.1226H36.3013V93.9976H83.6988V92.1226Z" fill="#868E99" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.5862 46.875C79.367 46.875 80 46.2314 80 45.4375C80 44.6436 79.367 44 78.5862 44H68.6897C67.9088 44 67.2759 44.6436 67.2759 45.4375C67.2759 47.8192 69.1748 49.75 71.5172 49.75C73.364 49.75 74.935 48.5499 75.5173 46.875H78.5862ZM40.4138 46.875C39.633 46.875 39 46.2314 39 45.4375C39 44.6436 39.633 44 40.4138 44H50.3103C51.0912 44 51.7241 44.6436 51.7241 45.4375C51.7241 47.8192 49.8252 49.75 47.4828 49.75C45.636 49.75 44.065 48.5499 43.4827 46.875H40.4138ZM55.7524 66.3213C55.888 66.0995 56.2309 65.69 56.7753 65.2677C57.698 64.552 58.8279 64.1248 60.2069 64.1248C61.5859 64.1248 62.7158 64.552 63.6385 65.2677C64.1829 65.69 64.5258 66.0995 64.6614 66.3213C65.0736 66.9955 65.9454 67.2023 66.6085 66.7831C67.2716 66.364 67.475 65.4776 67.0628 64.8034C66.7589 64.3064 66.199 63.6378 65.3535 62.9819C63.9596 61.9008 62.2381 61.2499 60.2069 61.2499C58.1757 61.2499 56.4542 61.9008 55.0603 62.9819C54.2148 63.6378 53.6549 64.3064 53.351 64.8034C52.9388 65.4776 53.1422 66.364 53.8053 66.7831C54.4684 67.2023 55.3402 66.9955 55.7524 66.3213Z" fill="#868E99" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="tc mb3">
|
||||
{{ $t('people.document_upload_zone_error') }}
|
||||
</p>
|
||||
<p class="tc">
|
||||
<input id="file" ref="file" type="file" @change="handleFileUpload()" />
|
||||
</p>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- LIST OF DOCUMENTS -->
|
||||
<div class="table">
|
||||
<div v-for="document in documents" :key="document.id" class="table-row">
|
||||
<div class="table-cell document-date f6">
|
||||
{{ formatTime(document.created_at) }}
|
||||
</div>
|
||||
<div class="table-cell document-type tc">
|
||||
<span class="document-type-icon">
|
||||
{{ document.type }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="table-cell">
|
||||
{{ document.original_filename }}
|
||||
</div>
|
||||
<div class="table-cell document-action relative">
|
||||
<div class="document-action-wrapper pointer" @click="toggleActionsModal(document.id)">
|
||||
<svg class="document-action-button" width="24" height="6" viewBox="0 0 24 6" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle cx="4.5" cy="2.5" r="2.5" fill="#505473" fill-opacity="0.86" />
|
||||
<circle cx="11.5" cy="2.5" r="2.5" fill="#505473" fill-opacity="0.86" />
|
||||
<circle cx="18.5" cy="2.5" r="2.5" fill="#505473" fill-opacity="0.86" />
|
||||
</svg>
|
||||
</div>
|
||||
<ul v-if="modalToDisplay === document.id" class="absolute bg-white z-max pv1 document-action-menu">
|
||||
<li class="tc">
|
||||
<a class="pv2 pointer ph3 inline-flex items-center w-100 no-underline document-action-menu-item" :href="document.link" target="_blank" @click="downloadDocument(document)">
|
||||
{{ $t('app.download') }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="tc">
|
||||
<a class="pv2 pointer ph3 inline-flex items-center no-underline w-100 document-action-menu-item delete" href="" @click.prevent="deleteDocument(document)">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
reachLimit: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
documents: [],
|
||||
displayUploadZone: false,
|
||||
displayUploadProgress: false,
|
||||
displayUploadError: false,
|
||||
file: '',
|
||||
uploadPercentage: 0,
|
||||
modalToDisplay: null,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
prepareComponent() {
|
||||
this.getDocuments();
|
||||
},
|
||||
|
||||
getDocuments() {
|
||||
axios.get('people/' + this.hash + '/documents')
|
||||
.then(response => {
|
||||
this.documents = response.data.data;
|
||||
});
|
||||
},
|
||||
|
||||
showUploadZone() {
|
||||
this.displayUploadZone = true;
|
||||
},
|
||||
|
||||
toggleActionsModal(id) {
|
||||
if (this.modalToDisplay === id) {
|
||||
this.modalToDisplay = null;
|
||||
} else {
|
||||
this.modalToDisplay = id;
|
||||
}
|
||||
},
|
||||
|
||||
handleFileUpload(){
|
||||
this.file = this.$refs.file.files[0];
|
||||
this.submitFile();
|
||||
},
|
||||
|
||||
formatTime(dateAsString) {
|
||||
var moment = require('moment-timezone');
|
||||
moment.locale(this._i18n.locale);
|
||||
|
||||
var date = moment(dateAsString);
|
||||
return date.format('ll');
|
||||
},
|
||||
|
||||
submitFile(){
|
||||
this.displayUploadZone = false;
|
||||
this.displayUploadProgress = true;
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('document', this.file);
|
||||
|
||||
axios.post( 'people/' + this.hash + '/documents',
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
},
|
||||
onUploadProgress: function( progressEvent ) {
|
||||
this.uploadPercentage = parseInt( Math.round( ( progressEvent.loaded * 100 ) / progressEvent.total ) );
|
||||
}.bind(this)
|
||||
}
|
||||
).then(response => {
|
||||
this.displayUploadProgress = false;
|
||||
this.documents.push(response.data);
|
||||
})
|
||||
.catch(error => {
|
||||
this.displayUploadProgress = false;
|
||||
this.file = null;
|
||||
this.displayUploadError = true;
|
||||
});
|
||||
},
|
||||
|
||||
downloadDocument(doc) {
|
||||
window.open(doc.link, '_blank');
|
||||
|
||||
// Close the modal menu
|
||||
this.modalToDisplay = null;
|
||||
},
|
||||
|
||||
deleteDocument(document) {
|
||||
axios.delete( 'people/' + this.hash + '/documents/' + document.id)
|
||||
.then(response => {
|
||||
this.documents.splice(this.documents.indexOf(document), 1);
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
460
resources/js/components/people/gifts/CreateGift.vue
Normal file
460
resources/js/components/people/gifts/CreateGift.vue
Normal file
@@ -0,0 +1,460 @@
|
||||
<style scoped>
|
||||
.photo {
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- Add a gift -->
|
||||
<transition name="fade">
|
||||
<div class="ba br3 mb3 pa3 b--black-40">
|
||||
<div class="pb3 mb3 flex-ns b--gray-monica">
|
||||
<!-- STATUS -->
|
||||
<form-radio
|
||||
:id="'status_idea'"
|
||||
v-model="newGift.status"
|
||||
:name="'status'"
|
||||
:required="true"
|
||||
:value="'idea'"
|
||||
:color="'success'"
|
||||
:full-class="'p-default p-fill p-curve'"
|
||||
>
|
||||
{{ $t('people.gifts_add_gift_idea') }}
|
||||
</form-radio>
|
||||
|
||||
<form-radio
|
||||
:id="'status_offered'"
|
||||
v-model="newGift.status"
|
||||
:name="'status'"
|
||||
:required="true"
|
||||
:value="'offered'"
|
||||
:color="'info'"
|
||||
:full-class="'p-default p-fill p-curve'"
|
||||
>
|
||||
{{ $t('people.gifts_add_gift_already_offered') }}
|
||||
</form-radio>
|
||||
|
||||
<form-radio
|
||||
:id="'status_received'"
|
||||
v-model="newGift.status"
|
||||
:name="'status'"
|
||||
:required="true"
|
||||
:value="'received'"
|
||||
:color="'warning'"
|
||||
:full-class="'p-default p-fill p-curve'"
|
||||
>
|
||||
{{ $t('people.gifts_add_gift_received') }}
|
||||
</form-radio>
|
||||
</div>
|
||||
|
||||
<div class="dt dt--fixed pb3 mb3 mb0-ns bb b--gray-monica">
|
||||
<!-- NAME -->
|
||||
<form-input
|
||||
:id="'name'"
|
||||
v-model="newGift.name"
|
||||
:input-type="'text'"
|
||||
:maxlength="255"
|
||||
:required="true"
|
||||
:class="'dtc pr2'"
|
||||
:title="$t('people.gifts_add_gift_name')"
|
||||
:validator="$v.newGift.name"
|
||||
@submit="store"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- ADDITIONAL FIELDS -->
|
||||
<div v-show="displayMenu" class="bb b--gray-monica pv3 mb3">
|
||||
<ul class="list">
|
||||
<li v-show="!displayComment" class="di pointer" :class="dirltr ? 'mr3' : 'ml3'">
|
||||
<a href="" @click.prevent="displayComment = true">{{ $t('people.gifts_add_comment') }}</a>
|
||||
</li>
|
||||
<li v-show="!displayUrl" class="di pointer" :class="dirltr ? 'mr3' : 'ml3'">
|
||||
<a href="" @click.prevent="displayUrl = true">{{ $t('people.gifts_add_link') }}</a>
|
||||
</li>
|
||||
<li v-show="!displayAmount" class="di pointer" :class="dirltr ? 'mr3' : 'ml3'">
|
||||
<a href="" @click.prevent="displayAmount = true; newGift.amount = 0;">{{ $t('people.gifts_add_value') }}</a>
|
||||
</li>
|
||||
<li v-if="familyContacts.length > 0" v-show="!displayRecipient" class="di pointer" :class="dirltr ? 'mr3' : 'ml3'">
|
||||
<a href="" @click.prevent="displayRecipient = true">{{ $t('people.gifts_add_recipient') }}</a>
|
||||
</li>
|
||||
<li v-if="!reachLimit" v-show="!displayUpload" class="di pointer" :class="dirltr ? 'mr3' : 'ml3'">
|
||||
<a href="" @click.prevent="() => { displayUpload = true; $refs.upload.showUploadZone(); }">{{ $t('people.gifts_add_photo') }}</a>
|
||||
</li>
|
||||
<li v-show="!displayDate" class="di pointer" :class="dirltr ? 'mr3' : 'ml3'">
|
||||
<a href="" @click.prevent="displayDate = true">{{ $t('people.gifts_add_date') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="displayComment" class="dt dt--fixed pb3 mb3 bb b--gray-monica">
|
||||
<!-- COMMENT -->
|
||||
<form-input
|
||||
:id="'comment'"
|
||||
v-model="newGift.comment"
|
||||
:input-type="'text'"
|
||||
:class="'dtc pr2'"
|
||||
:title="$t('people.gifts_add_comment')"
|
||||
@submit="store"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="displayUrl" class="dt dt--fixed pb3 mb3 bb b--gray-monica">
|
||||
<!-- URL -->
|
||||
<form-input
|
||||
:id="'url'"
|
||||
v-model="newGift.url"
|
||||
:input-type="'text'"
|
||||
:class="'dtc pr2'"
|
||||
:title="$t('people.gifts_add_link')"
|
||||
:placeholder="'https://'"
|
||||
@submit="store"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="displayDate" class="dt dt--fixed pb3 mb3 bb b--gray-monica">
|
||||
<!-- Date -->
|
||||
<form-date
|
||||
:id="'date'"
|
||||
v-model="newGift.date"
|
||||
:show-calendar-on-focus="true"
|
||||
:locale="locale"
|
||||
:class="[ dirltr ? 'fl dtc pr2' : 'fr dtc pr2' ]"
|
||||
:label="$t('people.gifts_add_date')"
|
||||
@submit="store"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="displayAmount" class="dt dt--fixed pb3 mb3 bb b--gray-monica">
|
||||
<!-- AMOUNT -->
|
||||
<form-input
|
||||
:id="'amount'"
|
||||
v-model="newGift.amount"
|
||||
:input-type="'number'"
|
||||
:class="'dtc pr2'"
|
||||
:title="$t('people.gifts_add_value')"
|
||||
:required="displayAmount"
|
||||
step=".01"
|
||||
@submit="store"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="displayRecipient" class="dt dt--fixed pb3 mb3 bb b--gray-monica">
|
||||
<!-- RECIPIENT -->
|
||||
<form-checkbox
|
||||
v-model="hasRecipient"
|
||||
:name="'has_recipient'"
|
||||
@change="(val) => { if (val) { $refs.recipient.focus() } }"
|
||||
>
|
||||
{{ $t('people.gifts_add_someone', {name: ''}) }}
|
||||
</form-checkbox>
|
||||
<form-select
|
||||
ref="recipient"
|
||||
v-model="newGift.recipient_id"
|
||||
:label="$t('people.gifts_add_recipient_field')"
|
||||
:options="familyContacts"
|
||||
:validator="$v.newGift.recipient_id"
|
||||
@input="hasRecipient = true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-show="displayUpload" class="dt dt--fixed pb3 mb3 bb b--gray-monica">
|
||||
<span class="mb2 b">
|
||||
{{ $t('people.gifts_add_photo_title') }}
|
||||
</span>
|
||||
|
||||
<photo-upload
|
||||
v-show="photos.length === 0"
|
||||
ref="upload"
|
||||
:hash="hash"
|
||||
:contact-id="contactId"
|
||||
@upload.stop="handlePhoto($event)"
|
||||
/>
|
||||
|
||||
<!-- LIST OF PHOTO -->
|
||||
<div v-show="photos.length > 0">
|
||||
<div class="flex flex-wrap">
|
||||
<div v-for="photo in photos" :key="photo.id" class="w-third-ns w-100">
|
||||
<div v-if="photo.id > 0" class="pa2 mb3 br2 ba b--gray-monica" :class="dirltr ? 'mr3' : 'ml3'">
|
||||
<div class="cover bg-center photo w-100 h-100 br2 bb b--gray-monica pb2"
|
||||
:style="'background-image: url(' + photo.link + ');'"
|
||||
>
|
||||
</div>
|
||||
<div class="pt2">
|
||||
<ul>
|
||||
<li>
|
||||
<a class="pointer" href="" @click.prevent="deletePhoto(photo)">
|
||||
{{ $t('people.photo_delete') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="ba br3 photo-upload-zone mb3 pa3">
|
||||
<div class="tc dib w-100 relative">
|
||||
{{ $tc('app.file_selected', photos.length, {count: photos.length}) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<error :errors="errors" />
|
||||
|
||||
<!-- ACTIONS -->
|
||||
<div class="pt3">
|
||||
<div class="flex-ns justify-between">
|
||||
<div class="">
|
||||
<a class="btn btn-secondary tc w-auto-ns w-100 mb2 pb0-ns" @click.prevent="close">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="">
|
||||
<button class="btn btn-primary w-auto-ns w-100 mb2 pb0-ns" @click.prevent="store">
|
||||
{{ gift ? $t('app.update') : $t('app.add') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Error from '../../partials/Error.vue';
|
||||
import PhotoUpload from '../photo/PhotoUpload.vue';
|
||||
import { validationMixin } from 'vuelidate';
|
||||
import { required, maxLength } from 'vuelidate/lib/validators';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Error,
|
||||
PhotoUpload
|
||||
},
|
||||
|
||||
mixins: [validationMixin],
|
||||
|
||||
model: {
|
||||
prop: 'gift',
|
||||
event: 'update'
|
||||
},
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
contactId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
gift: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
familyContacts: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
reachLimit: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
photos: [],
|
||||
displayComment: false,
|
||||
displayUrl: false,
|
||||
displayAmount: false,
|
||||
displayRecipient: false,
|
||||
displayUpload: false,
|
||||
displayDate: false,
|
||||
newGift: {
|
||||
name: '',
|
||||
status: 'idea',
|
||||
comment: null,
|
||||
url: null,
|
||||
amount: null,
|
||||
date: null,
|
||||
recipient_id: null,
|
||||
photo_id: null,
|
||||
},
|
||||
hasRecipient: false,
|
||||
errors: [],
|
||||
};
|
||||
},
|
||||
|
||||
validations() {
|
||||
var v = {
|
||||
newGift: {
|
||||
name: {
|
||||
required,
|
||||
maxLength: maxLength(255),
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if (this.hasRecipient) {
|
||||
v.newGift = Object.assign(v.newGift, {
|
||||
recipient_id: {
|
||||
required,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return v;
|
||||
},
|
||||
|
||||
computed: {
|
||||
locale() {
|
||||
return this.$root.locale;
|
||||
},
|
||||
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
},
|
||||
|
||||
displayMenu() {
|
||||
return !this.displayComment ||
|
||||
!this.displayUrl ||
|
||||
!this.displayAmount ||
|
||||
!this.displayDate ||
|
||||
!(this.displayRecipient || this.familyContacts.length === 0) ||
|
||||
!(this.displayUpload || this.reachLimit);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
gift: function (val) {
|
||||
this.newGift = val;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.resetFields();
|
||||
},
|
||||
|
||||
methods: {
|
||||
resetFields() {
|
||||
this.newGift.contact_id = this.contactId;
|
||||
if (this.gift) {
|
||||
this.newGift.contact_id = this.gift.contact.id;
|
||||
this.newGift.name = this.gift.name;
|
||||
this.newGift.comment = this.gift.comment;
|
||||
this.newGift.url = this.gift.url;
|
||||
this.newGift.amount = this.gift.amount;
|
||||
this.newGift.status = this.gift.status;
|
||||
this.newGift.recipient_id = this.gift.recipient ? this.gift.recipient.id : null;
|
||||
this.hasRecipient = this.newGift.recipient_id !== null;
|
||||
this.newGift.date = this.gift.date;
|
||||
this.photos = this.gift.photos;
|
||||
} else {
|
||||
this.newGift.name = '';
|
||||
this.newGift.comment = null;
|
||||
this.newGift.url = null;
|
||||
this.newGift.amount = null;
|
||||
this.newGift.status = 'idea';
|
||||
this.newGift.recipient_id = null;
|
||||
this.newGift.date = null;
|
||||
this.hasRecipient = false;
|
||||
}
|
||||
this.displayComment = this.gift ? this.gift.comment : false;
|
||||
this.displayDate = this.gift ? this.gift.date : false;
|
||||
this.displayUrl = this.gift ? this.gift.url : false;
|
||||
this.displayAmount = this.gift ? this.gift.amount !== '' : false;
|
||||
this.displayRecipient = this.gift ? (this.gift.recipient ? this.gift.recipient.id !== 0 : false) : false;
|
||||
this.displayUpload= this.gift ? this.gift.photos.length > 0 : false;
|
||||
|
||||
this.errors = [];
|
||||
this.$v.$reset();
|
||||
},
|
||||
|
||||
close() {
|
||||
this.resetFields();
|
||||
this.$emit('cancel');
|
||||
},
|
||||
|
||||
store() {
|
||||
if (! this.hasRecipient) {
|
||||
this.newGift.recipient_id = null;
|
||||
}
|
||||
|
||||
this.$v.$touch();
|
||||
|
||||
if (this.$v.$invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const method = this.gift ? 'put' : 'post';
|
||||
const url = `people/${this.hash}/gifts${this.gift ? '/'+this.gift.id : ''}`;
|
||||
|
||||
|
||||
const vm = this;
|
||||
axios[method](url, this.newGift)
|
||||
.then(response => {
|
||||
return vm.storePhoto(response);
|
||||
})
|
||||
.then(response => {
|
||||
//this.update(response);
|
||||
vm.close();
|
||||
vm.$emit('update', response.data.data);
|
||||
return response;
|
||||
})
|
||||
.then(() => {
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: vm.$t('people.gifts_add_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
vm._errorHandle(error);
|
||||
});
|
||||
},
|
||||
|
||||
storePhoto(response) {
|
||||
const vm = this;
|
||||
return this.$refs.upload.forceFileUpload()
|
||||
.then(photo => {
|
||||
if (photo !== undefined) {
|
||||
axios.put(`people/${this.hash}/gifts/${response.data.data.id}/photo/${photo.id}`);
|
||||
response.data.data.photos.push(photo);
|
||||
}
|
||||
return response;
|
||||
})
|
||||
.catch(error => {
|
||||
vm._errorHandle(error);
|
||||
return response;
|
||||
});
|
||||
},
|
||||
|
||||
_errorHandle(error) {
|
||||
if (error.response && typeof error.response.data === 'object') {
|
||||
this.errors = _.flatten(_.toArray(error.response.data));
|
||||
} else {
|
||||
this.errors = [vm.$t('app.error_try_again'), error.message];
|
||||
}
|
||||
},
|
||||
|
||||
deletePhoto(photo) {
|
||||
axios.delete(`people/${this.hash}/photos/${photo.id}`)
|
||||
.then(response => {
|
||||
this.photos.splice(this.photos.indexOf(photo), 1);
|
||||
if (this.photos.length === 0) {
|
||||
this.$refs.upload.showUploadZone();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handlePhoto(event) {
|
||||
this.photos.push({ id: -1, link: '' });
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
120
resources/js/components/people/gifts/Gift.vue
Normal file
120
resources/js/components/people/gifts/Gift.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<style scoped>
|
||||
.photo {
|
||||
height: 200px;
|
||||
}
|
||||
.gift-date{
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p class="mb1 bb b--gray-monica pa2">
|
||||
<span>
|
||||
<a v-if="gift.url" :href="gift.url" rel="noopener noreferrer" target="_blank">
|
||||
<strong>{{ gift.name }}</strong>
|
||||
</a>
|
||||
<strong v-else>{{ gift.name }}</strong>
|
||||
</span>
|
||||
<span v-if="gift.date" class="f7 gift-date">{{ formatDate(gift.date) }}</span>
|
||||
|
||||
<span v-if="gift.recipient && gift.recipient.complete_name">
|
||||
<span class="black-50 mr1 ml1">
|
||||
•
|
||||
</span>
|
||||
{{ $t('people.gifts_for', { name: gift.recipient.complete_name }) }}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<!-- LIST OF PHOTO -->
|
||||
<div v-if="gift.photos !== undefined && gift.photos.length > 0" class="bb b--gray-monica pa1">
|
||||
<div class="flex flex-wrap">
|
||||
<div v-for="photo in gift.photos" :key="photo.id" class="w-third-ns w-100">
|
||||
<div class="pa2 mb1 br2 ba b--gray-monica" :class="dirltr ? 'mr3' : 'ml3'">
|
||||
<div class="cover bg-center photo w-100 h-auto br2 bb b--gray-monica pb2"
|
||||
:style="'background-image: url(' + photo.link + ');'"
|
||||
@click.prevent="modalPhoto(photo)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="gift.amount || gift.comment" class="f6 pv1 mb1 ph2 pb2 bb b--gray-monica">
|
||||
<span v-if="gift.amount">
|
||||
{{ gift.amount_with_currency }}
|
||||
</span>
|
||||
<span v-if="gift.amount && gift.comment" class="black-50 mr1 ml1">
|
||||
•
|
||||
</span>
|
||||
<a v-if="gift.comment" class="pointer" href="" @click.prevent="comment = !comment">
|
||||
{{ $t('people.gifts_view_comment') }}
|
||||
</a>
|
||||
<div v-if="comment" class="mb1 mt1">
|
||||
{{ gift.comment }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ph2 pb2 cf f7">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<!-- MODAL ZOOM PHOTO -->
|
||||
<sweet-modal ref="modalPhoto" tabindex="-1" role="dialog" :enable-mobile-fullscreen="true" width="33%">
|
||||
<img :src="url" :alt="$t('people.photo_title')" class="mw-90 h-auto mb3" />
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
SweetModal,
|
||||
},
|
||||
|
||||
props: {
|
||||
gift: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
comment: false,
|
||||
showModal: false,
|
||||
url: '',
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
modalPhoto(photo) {
|
||||
this.url = photo.link;
|
||||
this.$refs.modalPhoto.open();
|
||||
},
|
||||
|
||||
formatDate(dateAsString) {
|
||||
const moment = require('moment-timezone');
|
||||
moment.locale(this._i18n.locale);
|
||||
moment.tz.setDefault('UTC');
|
||||
|
||||
var date = moment.tz(moment(dateAsString), this.$root.timezone);
|
||||
|
||||
return date.format('LL');
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
257
resources/js/components/people/gifts/Gifts.vue
Normal file
257
resources/js/components/people/gifts/Gifts.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<div>
|
||||
<notifications group="main" position="bottom right" />
|
||||
|
||||
<!-- Title -->
|
||||
<div>
|
||||
<img src="img/people/gifts.svg" :alt="$t('people.gifts_title')" class="icon-section icon-tasks" />
|
||||
<h3>
|
||||
{{ $t('people.gifts_title') }}
|
||||
<a v-cy-name="'add-gift-button'" href="" class="btn f6 pt2" :class="[ dirltr ? 'fr' : 'fl' ]"
|
||||
@click.prevent="displayCreateGift = true"
|
||||
>
|
||||
{{ $t('people.gifts_add_gift') }}
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<template v-if="displayCreateGift">
|
||||
<create-gift
|
||||
:hash="hash"
|
||||
:contact-id="contactId"
|
||||
:family-contacts="familyContacts"
|
||||
:reach-limit="reachLimit"
|
||||
@update="updateList($event)"
|
||||
@cancel="displayCreateGift = false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Listing -->
|
||||
<div>
|
||||
<ul class="mb3">
|
||||
<li class="di">
|
||||
<p class="di pointer" :class="[activeTab === 'idea' ? 'b' : 'black-50', dirltr ? 'mr3' : 'ml3']"
|
||||
@click.prevent="setActiveTab('idea')"
|
||||
>
|
||||
{{ $t('people.gifts_ideas') }} ({{ ideas.length }})
|
||||
</p>
|
||||
</li>
|
||||
<li class="di">
|
||||
<p class="di pointer" :class="[activeTab === 'offered' ? 'b' : 'black-50', dirltr ? 'mr3' : 'ml3']"
|
||||
@click.prevent="setActiveTab('offered')"
|
||||
>
|
||||
{{ $t('people.gifts_offered') }} ({{ offered.length }})
|
||||
</p>
|
||||
</li>
|
||||
<li class="di">
|
||||
<p class="di pointer" :class="[activeTab === 'received' ? 'b' : 'black-50', dirltr ? 'mr3' : 'ml3']"
|
||||
@click.prevent="setActiveTab('received')"
|
||||
>
|
||||
{{ $t('people.gifts_received') }} ({{ received.length }})
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div v-for="gift in filteredGifts" :key="gift.id" v-cy-name="'gift-item-' + gift.id" class="ba b--gray-monica mb3 br2">
|
||||
<gift v-if="!gift.edit"
|
||||
:gift="gift"
|
||||
@update="($event) => { updateList($event) }"
|
||||
>
|
||||
<div :class="dirltr ? 'fl' : 'fr'">
|
||||
<a v-if="gift.status === 'idea'" class="di" href="" @click.prevent="toggle(gift)">
|
||||
{{ $t('people.gifts_mark_offered') }}
|
||||
</a>
|
||||
<a v-if="gift.status === 'offered'" class="di" href="" @click.prevent="toggle(gift)">
|
||||
{{ $t('people.gifts_offered_as_an_idea') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div :class="dirltr ? 'fr' : 'fl'">
|
||||
<a v-cy-name="'edit-gift-button-' + gift.id" :class="dirltr ? 'mr1' : 'ml1'" class="di" href=""
|
||||
@click.prevent="$set(gift, 'edit', true)"
|
||||
>
|
||||
{{ $t('app.edit') }}
|
||||
</a>
|
||||
<a v-cy-name="'delete-gift-button-' + gift.id" :class="dirltr ? 'mr1' : 'ml1'" class="di" href=""
|
||||
@click.prevent="showDeleteModal(gift)"
|
||||
>
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</div>
|
||||
</gift>
|
||||
<create-gift
|
||||
v-else
|
||||
:hash="hash"
|
||||
:gift="gift"
|
||||
:contact-id="contactId"
|
||||
:family-contacts="familyContacts"
|
||||
:reach-limit="reachLimit"
|
||||
@update="updateGift(gift, $event)"
|
||||
@cancel="$set(gift, 'edit', false)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sweet-modal ref="modal" overlay-theme="dark" :title="$t('people.gifts_delete_title')">
|
||||
<form>
|
||||
<div class="mb4">
|
||||
{{ $t('people.gifts_delete_confirmation') }}
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeDeleteModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a v-cy-name="'modal-delete-gift-button-' + giftToTrash.id" class="btn btn-primary" href="" @click.prevent="trash(giftToTrash)">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
import Gift from './Gift.vue';
|
||||
import CreateGift from './CreateGift.vue';
|
||||
import moment from 'moment';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
Gift,
|
||||
CreateGift,
|
||||
SweetModal,
|
||||
},
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
contactId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
giftsActiveTab: {
|
||||
type: String,
|
||||
default: 'idea',
|
||||
},
|
||||
familyContacts: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
reachLimit: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
gifts: [],
|
||||
activeTab: '',
|
||||
giftToTrash: '',
|
||||
displayCreateGift: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
},
|
||||
|
||||
ideas() {
|
||||
return this.gifts.filter(gift => gift.status === 'idea');
|
||||
},
|
||||
|
||||
offered() {
|
||||
return this.gifts.filter(gift => gift.status === 'offered');
|
||||
},
|
||||
|
||||
received() {
|
||||
return this.gifts.filter(gift => gift.status === 'received');
|
||||
},
|
||||
|
||||
filteredGifts() {
|
||||
const vm = this;
|
||||
return this.gifts.filter(gift => gift.status === vm.activeTab);
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getGifts();
|
||||
this.setActiveTab(this.giftsActiveTab);
|
||||
},
|
||||
|
||||
setActiveTab(view) {
|
||||
this.activeTab = view === 'ideas' ? 'idea' : view;
|
||||
},
|
||||
|
||||
getGifts() {
|
||||
axios.get(`people/${this.hash}/gifts`)
|
||||
.then(response => {
|
||||
this.gifts = response.data.data;
|
||||
});
|
||||
},
|
||||
|
||||
toggle(gift) {
|
||||
if (gift.status === 'idea') {
|
||||
gift.status = 'offered';
|
||||
gift.date = moment().format('YYYY-MM-DD');
|
||||
} else {
|
||||
gift.status = 'idea';
|
||||
gift.date = null;
|
||||
}
|
||||
gift.contact_id = this.contactId;
|
||||
axios.put(`people/${this.hash}/gifts/${gift.id}`, gift)
|
||||
.then(response => {
|
||||
Vue.set(gift, 'status', response.data.data.status);
|
||||
Vue.set(gift, 'date', response.data.data.date);
|
||||
});
|
||||
},
|
||||
|
||||
showDeleteModal(gift) {
|
||||
this.$refs.modal.open();
|
||||
this.giftToTrash = gift;
|
||||
},
|
||||
|
||||
trash(gift) {
|
||||
axios.delete(`people/${this.hash}/gifts/${gift.id}`)
|
||||
.then(response => {
|
||||
this.gifts.splice(this.gifts.indexOf(gift), 1);
|
||||
this.closeDeleteModal();
|
||||
});
|
||||
},
|
||||
|
||||
updateList(activity) {
|
||||
this.displayCreateGift = false;
|
||||
this.getGifts();
|
||||
},
|
||||
|
||||
updateGift(gift, response) {
|
||||
this.$set(gift, 'edit', false);
|
||||
this.$set(gift, 'name', response.name);
|
||||
this.$set(gift, 'comment', response.comment);
|
||||
this.$set(gift, 'url', response.url);
|
||||
this.$set(gift, 'amount', response.amount);
|
||||
this.$set(gift, 'amount_with_currency', response.amount_with_currency);
|
||||
this.$set(gift, 'status', response.status);
|
||||
this.$set(gift, 'recipient', response.recipient);
|
||||
this.$set(gift, 'date', response.date);
|
||||
this.$set(gift, 'photos', response.photos);
|
||||
this.$emit('update', response);
|
||||
},
|
||||
|
||||
closeDeleteModal() {
|
||||
this.$refs.modal.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
294
resources/js/components/people/lifeevent/CreateLifeEvent.vue
Normal file
294
resources/js/components/people/lifeevent/CreateLifeEvent.vue
Normal file
@@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<section class="ph3 ph0-ns life-event">
|
||||
<notifications group="main" position="top middle" width="400" />
|
||||
|
||||
<div class="mt4 mw7 center mb3">
|
||||
<!-- Breadcrumb -->
|
||||
<ul v-if="view === 'types' || view === 'add'" class="ba b--gray-monica pa2 mb2">
|
||||
<li class="di">
|
||||
<a class="pointer" href="" @click.prevent="view = 'categories'">
|
||||
{{ $t('people.life_event_create_category') }}
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="view === 'types'" class="di">
|
||||
> {{ $t('people.life_event_category_' + activeCategory.default_life_event_category_key) }}
|
||||
</li>
|
||||
<template v-else-if="view === 'add'">
|
||||
<li class="di">
|
||||
> <a class="pointer" href="" @click.prevent="view = 'types'">
|
||||
{{ $t('people.life_event_category_' + activeCategory.default_life_event_category_key) }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="di">
|
||||
> {{ $t('people.life_event_create_life_event') }}
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
|
||||
<!-- List of events -->
|
||||
<ul v-if="view !== 'add'" class="ba b--gray-monica br2">
|
||||
<template v-if="view === 'categories'">
|
||||
<!-- CATEGORIES -->
|
||||
<li v-for="category in categories" :key="category.id" class="relative pointer bb b--gray-monica b--gray-monica pa2 life-event-add-row" @click="getType(category)">
|
||||
<div class="dib mr2">
|
||||
<img :src="'img/people/life-events/categories/' + category.default_life_event_category_key + '.svg'" :alt="category.default_life_event_category_key" style="min-width: 12px;" />
|
||||
</div>
|
||||
{{ $t('people.life_event_category_' + category.default_life_event_category_key) }}
|
||||
|
||||
<svg class="absolute life-event-add-arrow" width="10" height="13" viewBox="0 0 10 13" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M8.75071 5.66783C9.34483 6.06361 9.34483 6.93653 8.75072 7.33231L1.80442 11.9598C1.13984 12.4025 0.25 11.9261 0.25 11.1275L0.25 1.87263C0.25 1.07409 1.13984 0.59767 1.80442 1.04039L8.75071 5.66783Z" fill="#C4C4C4" />
|
||||
</svg>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<template v-else-if="view === 'types'">
|
||||
<!-- TYPES -->
|
||||
<li v-for="type in types" :key="type.id" class="relative pointer bb b--gray-monica b--gray-monica pa2 life-event-add-row" @click="displayAddScreen(type)">
|
||||
<div class="dib mr2">
|
||||
<img :src="'img/people/life-events/types/' + type.default_life_event_type_key + '.svg'" :alt="type.default_life_event_type_key" style="min-width: 12px;" />
|
||||
</div>
|
||||
<template v-if="type.name">
|
||||
{{ type.name }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t('people.life_event_sentence_' + type.default_life_event_type_key) }}
|
||||
</template>
|
||||
|
||||
<svg class="absolute life-event-add-arrow" width="10" height="13" viewBox="0 0 10 13" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M8.75071 5.66783C9.34483 6.06361 9.34483 6.93653 8.75072 7.33231L1.80442 11.9598C1.13984 12.4025 0.25 11.9261 0.25 11.1275L0.25 1.87263C0.25 1.07409 1.13984 0.59767 1.80442 1.04039L8.75071 5.66783Z" fill="#C4C4C4" />
|
||||
</svg>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
|
||||
<!-- ADD SCREEN -->
|
||||
<div v-else class="ba b--gray-monica br2 pt4">
|
||||
<div class="life-event-add-icon tc center">
|
||||
<img :src="'img/people/life-events/types/' + activeType.default_life_event_type_key + '.svg'" :alt="activeType.default_life_event_type_key" style="min-width: 17px;" />
|
||||
</div>
|
||||
|
||||
<h3 class="pt3 ph4 f3 fw5 tc">
|
||||
<template v-if="activeType.name">
|
||||
{{ activeType.name }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t('people.life_event_sentence_' + activeType.default_life_event_type_key) }}
|
||||
</template>
|
||||
</h3>
|
||||
|
||||
<!-- This field will be the same for every life event type no matter what, as the date is the only required field -->
|
||||
<div class="ph4 pv3 mb3 mb0-ns bb b--gray-monica">
|
||||
<label for="year" class="mr2">
|
||||
{{ $t('people.life_event_date_it_happened') }}
|
||||
</label>
|
||||
<div class="flex mb3">
|
||||
<div class="mr2">
|
||||
<form-select
|
||||
:id="'year'"
|
||||
v-model="selectedYear"
|
||||
:options="years"
|
||||
:title="''"
|
||||
:class="[ dirltr ? 'mr2' : '' ]" @input="updateDate"
|
||||
/>
|
||||
</div>
|
||||
<div class="mr2">
|
||||
<form-select
|
||||
:id="'month'"
|
||||
v-model="selectedMonth"
|
||||
:options="months"
|
||||
:title="''"
|
||||
:class="[ dirltr ? 'mr2' : '' ]" @input="updateDate"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<form-select
|
||||
:id="'day'"
|
||||
v-model="selectedDay"
|
||||
:options="days"
|
||||
:title="''"
|
||||
:class="[ dirltr ? '' : 'mr2' ]" @input="updateDate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="f6">
|
||||
{{ $t('people.life_event_create_date') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<create-default-life-event @contentChange="updateLifeEventContent($event)" />
|
||||
|
||||
<!-- YEARLY REMINDER -->
|
||||
<div class="ph4 pv3 mb3 mb0-ns bb b--gray-monica">
|
||||
<form-checkbox
|
||||
v-model.lazy="newLifeEvent.has_reminder"
|
||||
:name="'addReminder'"
|
||||
:dclass="[ dirltr ? 'mr3' : 'ml3' ]"
|
||||
>
|
||||
{{ $t('people.life_event_create_add_yearly_reminder') }}
|
||||
</form-checkbox>
|
||||
</div>
|
||||
|
||||
<!-- FORM ACTIONS -->
|
||||
<div class="ph4-ns ph3 pv3 bb b--gray-monica">
|
||||
<div class="flex-ns justify-between">
|
||||
<div>
|
||||
<a class="btn btn-secondary tc w-auto-ns w-100 mb2 pb0-ns" href="" @click.prevent="$emit('dismissModal')">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-primary w-auto-ns w-100 mb2 pb0-ns" @click="store()">
|
||||
{{ $t('app.add') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
|
||||
export default {
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
years: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
months: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
days: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedDay: 0,
|
||||
selectedMonth: 0,
|
||||
selectedYear: 0,
|
||||
newLifeEvent: {
|
||||
name: '',
|
||||
note: '',
|
||||
happened_at: '',
|
||||
life_event_type_id: 0,
|
||||
happened_at_month_unknown: false,
|
||||
happened_at_day_unknown: false,
|
||||
specific_information: '',
|
||||
has_reminder: false,
|
||||
},
|
||||
categories: [],
|
||||
activeCategory: '',
|
||||
activeType: '',
|
||||
types: [],
|
||||
view: 'categories',
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getCategories();
|
||||
this.newLifeEvent.happened_at = moment().format('YYYY-MM-DD');
|
||||
this.selectedYear = moment().year();
|
||||
this.selectedMonth = moment().month() + 1; // month is zero indexed (O_o) in moments.js
|
||||
this.selectedDay = moment().date();
|
||||
},
|
||||
|
||||
displayAddScreen(type) {
|
||||
this.view = 'add';
|
||||
this.activeType = type;
|
||||
this.newLifeEvent.life_event_type_id = type.id;
|
||||
},
|
||||
|
||||
getCategories() {
|
||||
axios.get('lifeevents/categories')
|
||||
.then(response => {
|
||||
this.categories = response.data.data;
|
||||
});
|
||||
},
|
||||
|
||||
getType(category) {
|
||||
axios.get('lifeevents/categories/' + category.id + '/types')
|
||||
.then(response => {
|
||||
this.types = response.data.data;
|
||||
});
|
||||
|
||||
this.view = 'types';
|
||||
this.activeCategory = category;
|
||||
},
|
||||
|
||||
updateLifeEventContent(lifeEvent) {
|
||||
this.newLifeEvent.note = lifeEvent.note;
|
||||
this.newLifeEvent.name = lifeEvent.name;
|
||||
this.newLifeEvent.specific_information = lifeEvent.specific_information;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the date when the user chooses either an empty month
|
||||
* or an empty day of the month.
|
||||
* If the user chooses an empty day, the day is set to 1 and we use
|
||||
* a boolean to indicate that the day is unknown.
|
||||
* Same for the month.
|
||||
*/
|
||||
updateDate() {
|
||||
if (this.selectedDay === 0 || this.selectedDay === '0') {
|
||||
this.newLifeEvent.happened_at_day_unknown = true;
|
||||
this.newLifeEvent.happened_at = this.selectedYear + '-' + this.selectedMonth + '-01';
|
||||
} else if (this.selectedMonth === 0 || this.selectedMonth === '0') {
|
||||
this.newLifeEvent.happened_at_month_unknown = true;
|
||||
this.newLifeEvent.happened_at_day_unknown = true;
|
||||
this.newLifeEvent.happened_at = this.selectedYear + '-01-01';
|
||||
this.selectedDay = 0;
|
||||
} else {
|
||||
this.newLifeEvent.happened_at = this.selectedYear + '-' + this.selectedMonth + '-' + this.selectedDay;
|
||||
this.newLifeEvent.happened_at_month_unknown = false;
|
||||
this.newLifeEvent.happened_at_day_unknown = false;
|
||||
}
|
||||
},
|
||||
|
||||
store() {
|
||||
axios.post('people/' + this.hash + '/lifeevents', this.newLifeEvent)
|
||||
.then(response => {
|
||||
this.$emit('updateLifeEventTimeline', response.data);
|
||||
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('people.life_event_create_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
649
resources/js/components/people/lifeevent/LifeEventList.vue
Normal file
649
resources/js/components/people/lifeevent/LifeEventList.vue
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="ph4 pv3 mb3 mb0-ns bb b--gray-monica">
|
||||
<label for="event-name" class="mr2">
|
||||
{{ $t('people.life_event_create_default_title') }}
|
||||
</label>
|
||||
<input id="event-name" v-model="defaultEvent.name" autofocus class="br2 f5 w-100 ba b--black-40 pa2 outline-0" @input="broadcastContentChange" />
|
||||
</div>
|
||||
|
||||
<div class="ph4 pv3 mb3 mb0-ns bb b--gray-monica">
|
||||
<label for="description" class="mr2">
|
||||
{{ $t('people.life_event_create_default_story') }}
|
||||
</label>
|
||||
<form-textarea
|
||||
id="description"
|
||||
v-model="defaultEvent.note"
|
||||
:required="false"
|
||||
:no-label="true"
|
||||
:rows="4"
|
||||
:placeholder="$t('people.life_event_create_default_description')"
|
||||
@input="broadcastContentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
defaultEvent: {
|
||||
name: '',
|
||||
note: '',
|
||||
specific_information: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.defaultEvent.happened_at = moment().format('YYYY-MM-DD');
|
||||
},
|
||||
|
||||
broadcastContentChange() {
|
||||
this.$emit('contentChange', this.defaultEvent);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
226
resources/js/components/people/partials/ContactAutosuggest.vue
Normal file
226
resources/js/components/people/partials/ContactAutosuggest.vue
Normal file
@@ -0,0 +1,226 @@
|
||||
<style lang="scss">
|
||||
.autosuggest__results-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.autosuggest__results {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
}
|
||||
.autosuggest__results-overflow {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
overflow: scroll;
|
||||
max-height: 361px;
|
||||
}
|
||||
.autosuggest__results-item {
|
||||
background: white;
|
||||
}
|
||||
.autosuggest__results-item:active,
|
||||
.autosuggest__results-item:hover,
|
||||
.autosuggest__results-item:focus,
|
||||
.autosuggest__results-item--highlighted {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<label
|
||||
v-if="title"
|
||||
class="mb2"
|
||||
:class="{ b: required }"
|
||||
:for="realid"
|
||||
>
|
||||
{{ title }}
|
||||
</label>
|
||||
<vue-autosuggest
|
||||
ref="autosuggest"
|
||||
:suggestions="items"
|
||||
:input-props="inputProps"
|
||||
:get-suggestion-value="getSuggestionValue"
|
||||
:component-attr-class-autosuggest-results="overflow ? 'autosuggest__results-overflow' : 'autosuggest__results'"
|
||||
@selected="selectHandler"
|
||||
@click="clickHandler"
|
||||
@blur="blurHandler"
|
||||
@input="updateItems"
|
||||
>
|
||||
<template slot-scope="{suggestion}">
|
||||
<component :is="componentItem" :item="suggestion.item" />
|
||||
</template>
|
||||
</vue-autosuggest>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { VueAutosuggest } from 'vue-autosuggest';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
VueAutosuggest,
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
addNoResult: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
placeholder : {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
componentItem : {
|
||||
type: Object,
|
||||
default: () => null
|
||||
},
|
||||
wait : {
|
||||
type: Number,
|
||||
default: 200,
|
||||
},
|
||||
minLen : {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
overflow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
inputClass : {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
filter: {
|
||||
type: Function,
|
||||
default: () => true,
|
||||
}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
items: [],
|
||||
callUpdateItems: null,
|
||||
cache: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
realid() {
|
||||
return this.id ? this.id : 'autosuggest__input';
|
||||
},
|
||||
inputProps() {
|
||||
return {
|
||||
id: this.realid,
|
||||
placeholder: this.placeholder,
|
||||
class: ['form-control', this.inputClass],
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.callUpdateItems = _.debounce((text) => {
|
||||
this.getContacts(text, this)
|
||||
.then((response) => {
|
||||
this.cache[text] = response;
|
||||
this.displayItems(text);
|
||||
});
|
||||
}, this.wait);
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
updateItems (text) {
|
||||
if (text === null || text === undefined) {
|
||||
return;
|
||||
}
|
||||
if (text.length < this.minLen) {
|
||||
this.items = [];
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.cache[text] === undefined) {
|
||||
this.callUpdateItems(text);
|
||||
} else {
|
||||
this.callUpdateItems.cancel();
|
||||
this.displayItems(text);
|
||||
}
|
||||
},
|
||||
|
||||
displayItems (text) {
|
||||
var datas = this.cache[text];
|
||||
|
||||
datas = datas.filter(this.filter);
|
||||
|
||||
this.items = [{ data: datas }];
|
||||
},
|
||||
|
||||
getContacts: function (keyword, vm) {
|
||||
return axios.post('people/search', {
|
||||
needle: keyword
|
||||
}).then(function(response) {
|
||||
const data = [];
|
||||
if (response.data.noResults === undefined || response.data.noResults === null) {
|
||||
response.data.data
|
||||
.forEach(function (contact) {
|
||||
contact.keyword = keyword;
|
||||
data.push(contact);
|
||||
});
|
||||
}
|
||||
data.push({
|
||||
id: -1,
|
||||
name: 'add_new_contact',
|
||||
keyword: keyword,
|
||||
});
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
clearCache() {
|
||||
this.cache = [];
|
||||
this.items = [];
|
||||
},
|
||||
|
||||
blurHandler(sender) {
|
||||
this.$emit('blur', sender);
|
||||
},
|
||||
|
||||
clickHandler(e) {
|
||||
this.loading = false;
|
||||
this.updateItems(this.value);
|
||||
},
|
||||
|
||||
selectHandler(suggestion) {
|
||||
if (!suggestion || !suggestion.item) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('select', suggestion);
|
||||
|
||||
this.$refs.autosuggest.searchInput = '';
|
||||
},
|
||||
|
||||
getSuggestionValue(suggestion) {
|
||||
if (!suggestion || !suggestion.item || suggestion.item.id < 0) {
|
||||
return;
|
||||
}
|
||||
return suggestion.item.complete_name.length > 0 ?
|
||||
suggestion.item.complete_name :
|
||||
suggestion.item.keyword;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
106
resources/js/components/people/partials/ContactItem.vue
Normal file
106
resources/js/components/people/partials/ContactItem.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<style lang="scss" scoped>
|
||||
.avatar-new {
|
||||
background-color: #fdb660;
|
||||
}
|
||||
.item-search-result {
|
||||
position: relative;
|
||||
background: transparent;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
background: transparent;
|
||||
|
||||
span {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
height: 36px;
|
||||
margin: 10px;
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
.avatar-initials {
|
||||
text-align: center;
|
||||
padding-top: 6px;
|
||||
font-size: 15px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: initial;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div v-if="item.id > 0" class="item-search-result" :data-contact="item.id" :data-name="item.name">
|
||||
<a :href="item.route">
|
||||
<img v-if="check"
|
||||
:class="className"
|
||||
:src="item.information.avatar.url"
|
||||
:alt="item.complete_name"
|
||||
@error="check=false"
|
||||
/>
|
||||
<div v-else
|
||||
:class="[className, 'avatar-initials']"
|
||||
:style="'background-color: '+item.information.avatar.default_avatar_color"
|
||||
>
|
||||
{{ item.initials }}
|
||||
</div>
|
||||
<template v-if="withName">
|
||||
{{ item.complete_name }}
|
||||
</template>
|
||||
<span></span>
|
||||
</a>
|
||||
</div>
|
||||
<div v-else class="item-search-result">
|
||||
<div class="avatar avatar-initials avatar-new">
|
||||
+
|
||||
</div>
|
||||
{{ $t('people.people_add_new') }}
|
||||
<span></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: null,
|
||||
},
|
||||
withName: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: 'avatar',
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
check: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
97
resources/js/components/people/partials/ContactMultiItem.vue
Normal file
97
resources/js/components/people/partials/ContactMultiItem.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<style lang="scss" scoped>
|
||||
.avatar-new {
|
||||
background-color: #fdb660;
|
||||
}
|
||||
.item-search-result {
|
||||
position: relative;
|
||||
background: transparent;
|
||||
border: 1px solid #c4cdd5;
|
||||
|
||||
.item-search-result-result {
|
||||
color: inherit;
|
||||
vertical-align: middle;
|
||||
background: transparent;
|
||||
|
||||
span {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 3px;
|
||||
display: inline-block;
|
||||
height: 36px;
|
||||
margin: 10px;
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
.avatar-initials {
|
||||
text-align: center;
|
||||
padding-top: 6px;
|
||||
font-size: 15px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.avatar-no-results {
|
||||
color: #fdb660;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-top: initial;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div v-if="item.id > 0" class="item-search-result" :data-contact="item.id" :data-name="item.name">
|
||||
<div class="item-search-result-result pointer">
|
||||
<img v-if="check"
|
||||
class="avatar"
|
||||
:src="item.information.avatar.url"
|
||||
:alt="item.complete_name"
|
||||
@error="check=false"
|
||||
/>
|
||||
<div v-else
|
||||
class="avatar avatar-initials"
|
||||
:style="'background-color: '+item.information.avatar.default_avatar_color"
|
||||
>
|
||||
{{ item.initials }}
|
||||
</div>
|
||||
{{ item.complete_name }}
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="item-search-result">
|
||||
<div class="item-search-result-result pointer">
|
||||
<div class="avatar avatar-initials avatar-no-results">
|
||||
.
|
||||
</div>
|
||||
{{ $t('people.people_search_no_results') }}
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
check: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
279
resources/js/components/people/photo/PhotoList.vue
Normal file
279
resources/js/components/people/photo/PhotoList.vue
Normal file
@@ -0,0 +1,279 @@
|
||||
<style scoped>
|
||||
.photo {
|
||||
height: 250px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="">
|
||||
<h3>
|
||||
📄 {{ $t('people.photo_list_title') }}
|
||||
<span v-if="reachLimit === 'false'" class="fr relative" style="top: -7px;">
|
||||
<a v-if="!onUpload" class="btn" href=""
|
||||
@click.prevent="() => { onUpload = true; $refs.upload.showUploadZone(); }"
|
||||
>
|
||||
{{ $t('people.photo_list_cta') }}
|
||||
</a>
|
||||
<a v-else class="btn" href=""
|
||||
@click.prevent="() => { onUpload = false; $refs.upload.cancelUpload(); }"
|
||||
>
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<p v-show="reachLimit === 'true'">
|
||||
{{ $t('settings.storage_upgrade_notice') }}
|
||||
</p>
|
||||
|
||||
<!-- EMPTY STATE -->
|
||||
<div v-if="!onUpload && photos.length === 0" class="ltr w-100 pt2">
|
||||
<div class="section-blank">
|
||||
<h3 class="mb4 mt3">
|
||||
{{ $t('people.photo_list_blank_desc') }}
|
||||
</h3>
|
||||
<img src="img/people/photos/photos_empty.svg" :alt="$t('people.photo_title')" class="w-50 center" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<photo-upload
|
||||
ref="upload"
|
||||
:hash="hash"
|
||||
:contact-id="contactId"
|
||||
@newphoto="handleNewPhoto($event)"
|
||||
/>
|
||||
|
||||
<!-- LIST OF PHOTO -->
|
||||
<div class="db mt3">
|
||||
<div class="flex flex-wrap">
|
||||
<div v-for="photo in photos" :key="photo.id" class="w-third-ns w-100 pointer">
|
||||
<div class="pa3 mb3 br2 ba b--gray-monica" :class="dirltr ? 'mr3' : 'ml3'">
|
||||
<div class="cover bg-center photo w-100 h-100 br2 bb b--gray-monica pb2"
|
||||
:style="'background-image: url(' + photo.link + ');'"
|
||||
@click.prevent="modalPhoto(photo)"
|
||||
>
|
||||
</div>
|
||||
<div class="pt2">
|
||||
<ul>
|
||||
<li v-show="currentPhotoIdAsAvatar === photo.id">
|
||||
🤩 {{ $t('people.photo_current_profile_pic') }}
|
||||
</li>
|
||||
<li v-show="currentPhotoIdAsAvatar !== photo.id">
|
||||
<a class="pointer" @click.prevent="makeProfilePicture(photo)">
|
||||
{{ $t('people.photo_make_profile_pic') }}
|
||||
</a>
|
||||
</li>
|
||||
<li v-show="confirmDestroyPhotoId !== photo.id">
|
||||
<a class="pointer" href="" @click.prevent="confirmDestroyPhotoId = photo.id">
|
||||
{{ $t('people.photo_delete') }}
|
||||
</a>
|
||||
</li>
|
||||
<li v-show="confirmDestroyPhotoId === photo.id">
|
||||
<a class="pointer" href="" @click.prevent="confirmDestroyPhotoId = 0">
|
||||
{{ $t('app.cancel') }}
|
||||
</a> <a class="pointer" href="" @click.prevent="deletePhoto(photo)">
|
||||
{{ $t('app.delete_confirm') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MODAL ZOOM PHOTO -->
|
||||
<transition v-if="showModal" name="modal">
|
||||
<div class="modal-mask">
|
||||
<div class="modal-wrapper">
|
||||
<div class="modal-container">
|
||||
<img :src="url" :alt="$t('people.photo_title')" class="mw-90 h-auto mb3" />
|
||||
|
||||
<ul class="list pl0 tc">
|
||||
<li class="di mr3">
|
||||
<a v-if="canShowPrev" class="pointer" @click="displayPrev">{{ $t('people.photo_previous') }}</a>
|
||||
</li>
|
||||
<li class="di mr3">
|
||||
<button class="btn" @click="showModal = false">
|
||||
{{ $t('app.close') }}
|
||||
</button>
|
||||
</li>
|
||||
<li class="di">
|
||||
<a v-if="canShowNext" class="pointer" @click="displayNext">{{ $t('people.photo_next') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import PhotoUpload from './PhotoUpload.vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
PhotoUpload,
|
||||
},
|
||||
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
contactId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
reachLimit: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
currentPhotoIdAsAvatar: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
photos: [],
|
||||
file: '',
|
||||
uploadPercentage: 0,
|
||||
confirmDestroyPhotoId: 0,
|
||||
showModal: false,
|
||||
url: '',
|
||||
onUpload: false,
|
||||
canShowPrev: false,
|
||||
canShowNext: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
prepareComponent() {
|
||||
this.getPhotos();
|
||||
},
|
||||
|
||||
getPhotos() {
|
||||
axios.get('people/' + this.hash + '/photos')
|
||||
.then(response => {
|
||||
this.photos = response.data.data;
|
||||
});
|
||||
},
|
||||
|
||||
handleNewPhoto(photo) {
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('app.default_save_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
this.photos.push(photo);
|
||||
},
|
||||
|
||||
deletePhoto(photo) {
|
||||
axios.delete( 'people/' + this.hash + '/photos/' + photo.id)
|
||||
.then(response => {
|
||||
this.photos.splice(this.photos.indexOf(photo), 1);
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('app.default_save_success'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
makeProfilePicture(photo) {
|
||||
axios.post( 'people/' + this.hash + '/makeProfilePicture/' + photo.id)
|
||||
.then(response => {
|
||||
window.location.href = 'people/' + this.hash;
|
||||
});
|
||||
},
|
||||
|
||||
modalPhoto(photo) {
|
||||
this.modal_photo = photo;
|
||||
this.url = photo.link;
|
||||
|
||||
if(this.modalHasNext()) {
|
||||
this.canShowNext = true;
|
||||
} else {
|
||||
this.canShowNext = false;
|
||||
}
|
||||
|
||||
if(this.modalHasPrev()) {
|
||||
this.canShowPrev = true;
|
||||
} else {
|
||||
this.canShowPrev = false;
|
||||
}
|
||||
|
||||
this.showModal = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* checks whether the there is a photo to the left of the current
|
||||
* photo shown in the modal
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
modalHasNext() {
|
||||
const index = this.photos.indexOf(this.modal_photo);
|
||||
|
||||
return index < this.photos.length-1;
|
||||
},
|
||||
|
||||
/**
|
||||
* checks whether the there is a photo to the right of the current
|
||||
* photo shown in the modal
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
modalHasPrev() {
|
||||
const index = this.photos.indexOf(this.modal_photo);
|
||||
|
||||
return index > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* set photo in modal located to the right of current modal photo
|
||||
* from our photos list
|
||||
*/
|
||||
displayNext() {
|
||||
const index = this.photos.indexOf(this.modal_photo);
|
||||
|
||||
const photo = this.photos[index+1];
|
||||
|
||||
this.modalPhoto(photo);
|
||||
},
|
||||
|
||||
/**
|
||||
* set photo in modal located to the left of current modal photo
|
||||
* from our photos list
|
||||
*/
|
||||
displayPrev() {
|
||||
const index = this.photos.indexOf(this.modal_photo);
|
||||
|
||||
const photo = this.photos[index-1];
|
||||
|
||||
this.modalPhoto(photo);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
179
resources/js/components/people/photo/PhotoUpload.vue
Normal file
179
resources/js/components/people/photo/PhotoUpload.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<style scoped lang="scss">
|
||||
.photo-upload-zone {
|
||||
background: #fff;
|
||||
border: 1px solid #d6d6d6;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
progress {
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
height: 8px;
|
||||
margin-bottom: 3px;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
background: #e2e7ee;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 1px 1px 0 rgba(255, 255, 255, 0.4);
|
||||
background-color: #329FF1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- FIRST STEP OF PHOTO UPLOAD -->
|
||||
<transition name="fade">
|
||||
<div v-if="displayUploadZone" class="ba br3 photo-upload-zone mb3 pa3">
|
||||
<div class="tc">
|
||||
</div>
|
||||
<div class="tc dib w-100 relative">
|
||||
<button class="btn">
|
||||
{{ $t('people.photo_upload_zone_cta') }}
|
||||
</button>
|
||||
<input id="file" ref="file" type="file" class="absolute o-0 w-100 h-100 pointer" style="left:0;"
|
||||
@change="handleFileUpload($event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- LAST STEP OF PHOTO UPLOAD -->
|
||||
<div v-if="displayUploadProgress" class="ba br3 photo-upload-zone mb3 pa3">
|
||||
<p class="tc mb1">
|
||||
{{ $t('people.document_upload_zone_progress') }}
|
||||
</p>
|
||||
<div class="tc mb1">
|
||||
<progress max="100" :value.prop="uploadPercentage"></progress>
|
||||
</div>
|
||||
<p class="tc f6 mb0">
|
||||
{{ $t('app.percent_uploaded', {percent: uploadPercentage}) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- ERROR STEP WHEN UPLOADING A PHOTO -->
|
||||
<transition name="fade">
|
||||
<div v-if="displayUploadError" class="ba br3 photo-upload-zone mb3 pa3">
|
||||
<div class="tc">
|
||||
<svg width="120" height="150" viewBox="0 0 120 150" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M27.2012 35.9138V101.562H92.7999V18.4375H44.6762L27.2012 35.9138ZM44.1274 21.6388V35.365H30.4024L44.1274 21.6388ZM46.0024 20.3125H90.9249V99.6875H29.0762V37.24H46.0024V20.3125Z" fill="#868E99" />
|
||||
<path d="M83.6988 72.3676H36.3013V74.2426H83.6988V72.3676Z" fill="#868E99" />
|
||||
<path d="M83.6988 82.245H36.3013V84.12H83.6988V82.245Z" fill="#868E99" />
|
||||
<path d="M83.6988 92.1226H36.3013V93.9976H83.6988V92.1226Z" fill="#868E99" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M78.5862 46.875C79.367 46.875 80 46.2314 80 45.4375C80 44.6436 79.367 44 78.5862 44H68.6897C67.9088 44 67.2759 44.6436 67.2759 45.4375C67.2759 47.8192 69.1748 49.75 71.5172 49.75C73.364 49.75 74.935 48.5499 75.5173 46.875H78.5862ZM40.4138 46.875C39.633 46.875 39 46.2314 39 45.4375C39 44.6436 39.633 44 40.4138 44H50.3103C51.0912 44 51.7241 44.6436 51.7241 45.4375C51.7241 47.8192 49.8252 49.75 47.4828 49.75C45.636 49.75 44.065 48.5499 43.4827 46.875H40.4138ZM55.7524 66.3213C55.888 66.0995 56.2309 65.69 56.7753 65.2677C57.698 64.552 58.8279 64.1248 60.2069 64.1248C61.5859 64.1248 62.7158 64.552 63.6385 65.2677C64.1829 65.69 64.5258 66.0995 64.6614 66.3213C65.0736 66.9955 65.9454 67.2023 66.6085 66.7831C67.2716 66.364 67.475 65.4776 67.0628 64.8034C66.7589 64.3064 66.199 63.6378 65.3535 62.9819C63.9596 61.9008 62.2381 61.2499 60.2069 61.2499C58.1757 61.2499 56.4542 61.9008 55.0603 62.9819C54.2148 63.6378 53.6549 64.3064 53.351 64.8034C52.9388 65.4776 53.1422 66.364 53.8053 66.7831C54.4684 67.2023 55.3402 66.9955 55.7524 66.3213Z" fill="#868E99" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="tc mb3">
|
||||
{{ $t('people.document_upload_zone_error') }}
|
||||
</p>
|
||||
<p class="tc">
|
||||
<input id="file" ref="file" type="file" @change="handleFileUpload($event)" />
|
||||
</p>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
hash: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
contactId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
reachLimit: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
currentPhotoIdAsAvatar: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
displayUploadZone: false,
|
||||
displayUploadProgress: false,
|
||||
displayUploadError: false,
|
||||
file: '',
|
||||
uploadPercentage: 0,
|
||||
confirmDestroyPhotoId: 0,
|
||||
url: '',
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
showUploadZone() {
|
||||
this.displayUploadZone = true;
|
||||
},
|
||||
|
||||
inUpload() {
|
||||
return this.displayUploadZone || this.displayUploadError || this.displayUploadProgress;
|
||||
},
|
||||
|
||||
cancelUpload() {
|
||||
this.displayUploadZone = false;
|
||||
this.displayUploadError = false;
|
||||
this.displayUploadProgress = false;
|
||||
},
|
||||
|
||||
handleFileUpload(event){
|
||||
this.$emit('upload', event);
|
||||
if (!event.cancelBubble) {
|
||||
this.forceFileUpload();
|
||||
}
|
||||
},
|
||||
|
||||
forceFileUpload(){
|
||||
const f = this.$refs.file !== undefined ? this.$refs.file.files[0] : undefined;
|
||||
if (f === undefined) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.file = f;
|
||||
return this.submitFile();
|
||||
},
|
||||
|
||||
submitFile(){
|
||||
this.displayUploadZone = false;
|
||||
this.displayUploadProgress = true;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('photo', this.file);
|
||||
|
||||
return axios.post(`people/${this.hash}/photos`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
},
|
||||
onUploadProgress: function( progressEvent ) {
|
||||
this.uploadPercentage = parseInt( Math.round( ( progressEvent.loaded * 100 ) / progressEvent.total ) );
|
||||
}.bind(this)
|
||||
}
|
||||
).then(response => {
|
||||
this.displayUploadProgress = false;
|
||||
|
||||
this.$emit('newphoto', response.data.data);
|
||||
|
||||
return response.data.data;
|
||||
})
|
||||
.catch(error => {
|
||||
this.displayUploadProgress = false;
|
||||
this.file = null;
|
||||
this.displayUploadError = true;
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
478
resources/js/components/settings/ActivityTypes.vue
Normal file
478
resources/js/components/settings/ActivityTypes.vue
Normal file
@@ -0,0 +1,478 @@
|
||||
<template>
|
||||
<div>
|
||||
<notifications group="activityTypes" position="bottom right" />
|
||||
|
||||
<h3 class="with-actions">
|
||||
{{ $t('settings.personalization_activity_type_category_title') }}
|
||||
<a v-if="!limited" v-cy-name="'add-activity-type-category-button'" class="btn nt2" :class="[ dirltr ? 'fr' : 'fl' ]" href=""
|
||||
@click.prevent="showCreateCategoryModal"
|
||||
>
|
||||
{{ $t('settings.personalization_activity_type_category_add') }}
|
||||
</a>
|
||||
</h3>
|
||||
<p>{{ $t('settings.personalization_activity_type_category_description') }}</p>
|
||||
|
||||
<div v-if="limited" v-cy-name="'activity-type-premium-message'" class="mt3 mb3 form-information-message br2">
|
||||
<div class="pa3 flex">
|
||||
<div class="mr3">
|
||||
<svg viewBox="0 0 20 20">
|
||||
<g fill-rule="evenodd">
|
||||
<circle cx="10" cy="10" r="9" fill="currentColor" /><path d="M10 0C4.486 0 0 4.486 0 10s4.486 10 10 10 10-4.486 10-10S15.514 0 10 0m0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8m1-5v-3a1 1 0 0 0-1-1H9a1 1 0 1 0 0 2v3a1 1 0 0 0 1 1h1a1 1 0 1 0 0-2m-1-5.9a1.1 1.1 0 1 0 0-2.2 1.1 1.1 0 0 0 0 2.2" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div v-html="$t('settings.personalisation_paid_upgrade_vue', {url: 'settings/subscriptions' })"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dt dt--fixed w-100 collapse br--top br--bottom">
|
||||
<div class="dt-row">
|
||||
<div class="dtc">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_activity_type_category_table_name') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_activity_type_category_table_actions') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-cy-name="'activity-types'">
|
||||
<ul v-cy-name="'activity-type-categories'" v-cy-items="activityTypeCategories.map(a => a.id)">
|
||||
<li v-for="activityTypeCategory in activityTypeCategories" :key="activityTypeCategory.id" v-cy-name="'activity-types-'+activityTypeCategory.id"
|
||||
v-cy-items="activityTypeCategory.activityTypes ? activityTypeCategory.activityTypes.map(a => a.id) : ''" class="dt dt--fixed w-100 collapse br--top br--bottom mt3"
|
||||
>
|
||||
<!-- ACTIVITY TYPE CATEGORY -->
|
||||
<div class="dt-row hover bb b--light-gray">
|
||||
<div class="dtc">
|
||||
<div class="pa2 b">
|
||||
<strong>{{ activityTypeCategory.name }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc">
|
||||
<div class="pa2" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<em v-if="!limited" v-cy-name="'activity-type-category-edit-button-'+activityTypeCategory.id"
|
||||
class="fa fa-pencil-square-o pointer pr2" @click="showEditCategory(activityTypeCategory)"
|
||||
></em>
|
||||
<em v-if="!limited" v-cy-name="'activity-type-category-delete-button-'+activityTypeCategory.id"
|
||||
class="fa fa-trash-o pointer" @click="showDeleteCategory(activityTypeCategory)"
|
||||
></em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="activityType in activityTypeCategory.activityTypes" :key="activityType.id" class="dt-row hover bb b--light-gray">
|
||||
<div class="dtc">
|
||||
<div class="pa2 pl4">
|
||||
{{ activityType.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2">
|
||||
<em v-if="!limited" v-cy-name="'activity-type-edit-button-'+activityType.id"
|
||||
class="fa fa-pencil-square-o pointer pr2" @click="showEditType(activityType, activityTypeCategory.id)"
|
||||
></em>
|
||||
<em v-if="!limited" v-cy-name="'activity-type-delete-button-'+activityType.id"
|
||||
class="fa fa-trash-o pointer" @click="showDeleteType(activityType)"
|
||||
></em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!limited" class="dt-row">
|
||||
<div class="dtc">
|
||||
<div class="pa2 pl4">
|
||||
<a v-cy-name="'add-activity-type-button-for-category-'+activityTypeCategory.id" class="pointer" href=""
|
||||
@click.prevent="showCreateTypeModal(activityTypeCategory)"
|
||||
>
|
||||
{{ $t('settings.personalization_activity_type_add_button') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Create Activity Type Category -->
|
||||
<sweet-modal ref="createCategoryModal" overlay-theme="dark" :title="$t('settings.personalization_activity_type_category_modal_add')">
|
||||
<form @submit.prevent="storeCategory()">
|
||||
<div class="mb4">
|
||||
<p class="b mb2"></p>
|
||||
<form-input
|
||||
:id="'add-category-name'"
|
||||
v-model="createCategoryForm.name"
|
||||
:input-type="'text'"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_activity_type_category_modal_question')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeCategoryModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a v-cy-name="'add-activity-type-category-save-button'" class="btn btn-primary" href="" @click.prevent="storeCategory()">
|
||||
{{ $t('app.save') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
|
||||
<!-- Update Activity Type Category -->
|
||||
<sweet-modal ref="updateCategoryModal" overlay-theme="dark" :title="$t('settings.personalization_activity_type_category_modal_edit')">
|
||||
<form @submit.prevent="updateCategory()">
|
||||
<div class="mb4">
|
||||
<p class="b mb2"></p>
|
||||
<form-input
|
||||
:id="'update-category-name'"
|
||||
v-model="updateCategoryForm.name"
|
||||
:input-type="'text'"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_activity_type_category_modal_question')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeUpdateCategoryModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a v-cy-name="'update-activity-type-category-button'" class="btn btn-primary" href="" @click.prevent="updateCategory()">
|
||||
{{ $t('app.update') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
|
||||
<!-- Create Activity Type -->
|
||||
<sweet-modal ref="createTypeModal" overlay-theme="dark" :title="$t('settings.personalization_activity_type_modal_add')">
|
||||
<form @submit.prevent="storeType()">
|
||||
<div class="mb4">
|
||||
<p class="b mb2"></p>
|
||||
<form-input
|
||||
:id="'add-type-name'"
|
||||
v-model="createTypeForm.name"
|
||||
:input-type="'text'"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_activity_type_modal_question')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeCreateTypeModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a v-cy-name="'add-type-button'" class="btn btn-primary" href="" @click.prevent="storeType()">
|
||||
{{ $t('app.save') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
|
||||
<!-- Update Activity Type -->
|
||||
<sweet-modal ref="updateTypeModal" overlay-theme="dark" :title="$t('settings.personalization_activity_type_modal_edit')">
|
||||
<form @submit.prevent="updateType()">
|
||||
<div class="mb4">
|
||||
<p class="b mb2"></p>
|
||||
<form-input
|
||||
:id="'update-type-name'"
|
||||
v-model="updateTypeForm.name"
|
||||
:input-type="'text'"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_activity_type_modal_question')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeUpdateTypeModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a v-cy-name="'update-type-button'" class="btn btn-primary" href="" @click.prevent="updateType()">
|
||||
{{ $t('app.update') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
|
||||
<!-- Delete Activiy type category -->
|
||||
<sweet-modal ref="deleteCategoryModal" overlay-theme="dark" :title="$t('settings.personalization_activity_type_category_modal_delete')">
|
||||
<form>
|
||||
<div v-if="errorMessage !== ''" class="form-error-message mb3">
|
||||
<div class="pa2">
|
||||
<p class="mb0">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb4">
|
||||
<p class="mb2">
|
||||
{{ $t('settings.personalization_activity_type_category_modal_delete_desc') }}
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeDeleteCategoryModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a v-cy-name="'delete-category-button'" class="btn btn-primary" href="" @click.prevent="destroyCategory()">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
|
||||
<!-- Delete Activiy type -->
|
||||
<sweet-modal ref="deleteTypeModal" overlay-theme="dark" :title="$t('settings.personalization_activity_type_modal_delete')">
|
||||
<form>
|
||||
<div v-if="errorMessage !== ''" class="form-error-message mb3">
|
||||
<div class="pa2">
|
||||
<p class="mb0">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb4">
|
||||
<p class="mb2">
|
||||
{{ $t('settings.personalization_activity_type_modal_delete_desc') }}
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeDeleteTypeModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a v-cy-name="'delete-type-button'" class="btn btn-primary" href="" @click.prevent="destroyType()">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
SweetModal
|
||||
},
|
||||
|
||||
props: {
|
||||
limited: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
activityTypes: [],
|
||||
activityTypeCategories: [],
|
||||
errorMessage: '',
|
||||
|
||||
updatedCategory: {
|
||||
id: '',
|
||||
name: ''
|
||||
},
|
||||
|
||||
createCategoryForm: {
|
||||
name: '',
|
||||
errors: []
|
||||
},
|
||||
|
||||
createTypeForm: {
|
||||
name: '',
|
||||
activity_type_category_id: '',
|
||||
errors: []
|
||||
},
|
||||
|
||||
updateCategoryForm: {
|
||||
id: '',
|
||||
name: '',
|
||||
activity_type_category_id: '',
|
||||
errors: []
|
||||
},
|
||||
|
||||
updateTypeForm: {
|
||||
id: '',
|
||||
name: '',
|
||||
errors: []
|
||||
},
|
||||
|
||||
destroyCategoryForm: {
|
||||
id: '',
|
||||
errors: []
|
||||
},
|
||||
|
||||
destroyTypeForm: {
|
||||
id: '',
|
||||
errors: []
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getActivityTypeCategories();
|
||||
},
|
||||
|
||||
getActivityTypeCategories() {
|
||||
axios.get('settings/personalization/activitytypecategories')
|
||||
.then(response => {
|
||||
this.activityTypeCategories = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
closeCategoryModal() {
|
||||
this.$refs.createCategoryModal.close();
|
||||
},
|
||||
|
||||
closeDeleteCategoryModal() {
|
||||
this.$refs.deleteCategoryModal.close();
|
||||
},
|
||||
|
||||
showCreateCategoryModal() {
|
||||
this.$refs.createCategoryModal.open();
|
||||
},
|
||||
|
||||
storeCategory() {
|
||||
axios.post('settings/personalization/activitytypecategories', this.createCategoryForm)
|
||||
.then(response => {
|
||||
this.$refs.createCategoryModal.close();
|
||||
this.activityTypeCategories.push(response.data.data);
|
||||
this.createCategoryForm.name = '';
|
||||
|
||||
this.notify(this.$t('app.default_save_success'), true);
|
||||
});
|
||||
},
|
||||
|
||||
showEditCategory(category) {
|
||||
this.updateCategoryForm.id = category.id;
|
||||
this.updateCategoryForm.name = category.name;
|
||||
this.updatedCategory = category;
|
||||
|
||||
this.$refs.updateCategoryModal.open();
|
||||
},
|
||||
|
||||
showDeleteCategory(category) {
|
||||
this.destroyCategoryForm.id = category.id;
|
||||
|
||||
this.$refs.deleteCategoryModal.open();
|
||||
},
|
||||
|
||||
showDeleteType(type) {
|
||||
this.destroyTypeForm.id = type.id;
|
||||
|
||||
this.$refs.deleteTypeModal.open();
|
||||
},
|
||||
|
||||
showEditType(type, categoryId) {
|
||||
this.updateTypeForm.id = type.id;
|
||||
this.updateTypeForm.name = type.name;
|
||||
this.updateTypeForm.activity_type_category_id = categoryId;
|
||||
|
||||
this.$refs.updateTypeModal.open();
|
||||
},
|
||||
|
||||
closeUpdateCategoryModal() {
|
||||
this.$refs.updateCategoryModal.close();
|
||||
},
|
||||
|
||||
closeCreateTypeModal() {
|
||||
this.$refs.createTypeModal.close();
|
||||
},
|
||||
|
||||
closeUpdateTypeModal() {
|
||||
this.$refs.updateTypeModal.close();
|
||||
},
|
||||
|
||||
closeDeleteTypeModal() {
|
||||
this.$refs.deleteTypeModal.close();
|
||||
},
|
||||
|
||||
updateCategory() {
|
||||
axios.put('settings/personalization/activitytypecategories/' + this.updateCategoryForm.id, this.updateCategoryForm)
|
||||
.then(response => {
|
||||
this.$refs.updateCategoryModal.close();
|
||||
this.updatedCategory.name = this.updateCategoryForm.name;
|
||||
this.updateCategoryForm.name = '';
|
||||
|
||||
this.notify(this.$t('app.default_save_success'), true);
|
||||
});
|
||||
},
|
||||
|
||||
showCreateTypeModal(category) {
|
||||
this.$refs.createTypeModal.open();
|
||||
this.createTypeForm.activity_type_category_id = category.id;
|
||||
},
|
||||
|
||||
storeType() {
|
||||
axios.post('settings/personalization/activitytypes', this.createTypeForm)
|
||||
.then(response => {
|
||||
this.$refs.createTypeModal.close();
|
||||
this.activityTypes.push(response.data);
|
||||
this.createTypeForm.name = '';
|
||||
this.getActivityTypeCategories();
|
||||
|
||||
this.notify(this.$t('app.default_save_success'), true);
|
||||
});
|
||||
},
|
||||
|
||||
destroyCategory() {
|
||||
axios.delete('settings/personalization/activitytypecategories/' + this.destroyCategoryForm.id)
|
||||
.then(response => {
|
||||
this.$refs.deleteCategoryModal.close();
|
||||
this.destroyCategoryForm.id = '';
|
||||
this.getActivityTypeCategories();
|
||||
|
||||
this.notify(this.$t('app.default_save_success'), true);
|
||||
})
|
||||
.catch(error => {
|
||||
this.errorMessage = error.response.data.message;
|
||||
});
|
||||
},
|
||||
|
||||
updateType() {
|
||||
axios.put('settings/personalization/activitytypes/' + this.updateTypeForm.id, this.updateTypeForm)
|
||||
.then(response => {
|
||||
this.$refs.updateTypeModal.close();
|
||||
this.updatedCategory.name = this.updateTypeForm.name;
|
||||
this.updateTypeForm.name = '';
|
||||
this.getActivityTypeCategories();
|
||||
|
||||
this.notify(this.$t('app.default_save_success'), true);
|
||||
});
|
||||
},
|
||||
|
||||
destroyType() {
|
||||
axios.delete('settings/personalization/activitytypes/' + this.destroyTypeForm.id)
|
||||
.then(response => {
|
||||
this.$refs.deleteTypeModal.close();
|
||||
this.destroyTypeForm.id = '';
|
||||
this.getActivityTypeCategories();
|
||||
|
||||
this.notify(this.$t('app.default_save_success'), true);
|
||||
})
|
||||
.catch(error => {
|
||||
this.errorMessage = error.response.data.message;
|
||||
});
|
||||
},
|
||||
|
||||
notify(text, success) {
|
||||
this.$notify({
|
||||
group: 'activityTypes',
|
||||
title: text,
|
||||
text: '',
|
||||
type: success ? 'success' : 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
392
resources/js/components/settings/ContactFieldTypes.vue
Normal file
392
resources/js/components/settings/ContactFieldTypes.vue
Normal file
@@ -0,0 +1,392 @@
|
||||
<template>
|
||||
<div>
|
||||
<notifications group="main" position="bottom right" />
|
||||
|
||||
<h3 class="with-actions">
|
||||
{{ $t('settings.personalization_contact_field_type_title') }}
|
||||
<a class="btn nt2" :class="[ dirltr ? 'fr' : 'fl' ]" href="" @click.prevent="add">
|
||||
{{ $t('settings.personalization_contact_field_type_add') }}
|
||||
</a>
|
||||
</h3>
|
||||
<p>{{ $t('settings.personalization_contact_field_type_description') }}</p>
|
||||
|
||||
<div v-if="submitted" class="pa2 ba b--yellow mb3 mt3 br2 bg-washed-yellow">
|
||||
{{ $t('settings.personalization_contact_field_type_add_success') }}
|
||||
</div>
|
||||
|
||||
<div v-if="edited" class="pa2 ba b--yellow mb3 mt3 br2 bg-washed-yellow">
|
||||
{{ $t('settings.personalization_contact_field_type_edit_success') }}
|
||||
</div>
|
||||
|
||||
<div v-if="deleted" class="pa2 ba b--yellow mb3 mt3 br2 bg-washed-yellow">
|
||||
{{ $t('settings.personalization_contact_field_type_delete_success') }}
|
||||
</div>
|
||||
|
||||
<div class="dt dt--fixed w-100 collapse br--top br--bottom">
|
||||
<div class="dt-row">
|
||||
<div class="dtc">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_contact_field_type_table_name') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_contact_field_type_table_protocol') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_contact_field_type_table_actions') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="contactFieldType in contactFieldTypes" :key="contactFieldType.id" class="dt-row hover bb b--light-gray">
|
||||
<div class="dtc">
|
||||
<div class="pa2">
|
||||
<em v-if="contactFieldType.fontawesome_icon" :class="contactFieldType.fontawesome_icon" class="pr2"></em>
|
||||
<em v-else class="pr2 fa fa-address-card-o"></em>
|
||||
{{ contactFieldType.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc">
|
||||
<code class="f7">
|
||||
{{ contactFieldType.protocol }}
|
||||
</code>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2">
|
||||
<em class="fa fa-pencil-square-o pointer pr2" @click="edit(contactFieldType)"></em>
|
||||
<em v-if="contactFieldType.delible" class="fa fa-trash-o pointer" @click="showDelete(contactFieldType)"></em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Contact field type -->
|
||||
<sweet-modal ref="modalCreateContactFieldType" overlay-theme="dark"
|
||||
:title="$t('settings.personalization_contact_field_type_modal_title')"
|
||||
@open="_focusCreateInput"
|
||||
>
|
||||
<!-- Form Errors -->
|
||||
<errors :errors="createForm.errors" />
|
||||
|
||||
<form class="form-horizontal" role="form" @submit.prevent="store">
|
||||
<div class="form-group">
|
||||
<div class="col-md-auto">
|
||||
<form-input
|
||||
:id="'name'"
|
||||
ref="createName"
|
||||
v-model="createForm.name"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_contact_field_type_modal_name')"
|
||||
@submit="store"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-auto">
|
||||
<form-input
|
||||
:id="'protocol'"
|
||||
v-model="createForm.protocol"
|
||||
:placeholder="'mailto:'"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_contact_field_type_modal_protocol')"
|
||||
@submit="store"
|
||||
/>
|
||||
|
||||
<small class="form-text text-muted">
|
||||
{{ $t('settings.personalization_contact_field_type_modal_protocol_help') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-auto">
|
||||
<form-input
|
||||
:id="'icon'"
|
||||
v-model="createForm.icon"
|
||||
:placeholder="'fa fa-address-book-o'"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_contact_field_type_modal_icon')"
|
||||
@submit="store"
|
||||
/>
|
||||
|
||||
<small class="form-text text-muted">
|
||||
{{ $t('settings.personalization_contact_field_type_modal_icon_help') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeModal">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a class="btn btn-primary" href="" @click.prevent="store">
|
||||
{{ $t('app.save') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
|
||||
<!-- Edit Contact field type -->
|
||||
<sweet-modal ref="modalEditContactFieldType" overlay-theme="dark"
|
||||
:title="$t('settings.personalization_contact_field_type_modal_edit_title')"
|
||||
@open="_focusEditInput"
|
||||
>
|
||||
<!-- Form Errors -->
|
||||
<error :errors="editForm.errors" />
|
||||
|
||||
<form class="form-horizontal" role="form" @submit.prevent="update">
|
||||
<div class="form-group">
|
||||
<div class="col-md-auto">
|
||||
<form-input
|
||||
:id="'name'"
|
||||
ref="editName"
|
||||
v-model="editForm.name"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_contact_field_type_modal_name')"
|
||||
@submit="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-auto">
|
||||
<form-input
|
||||
:id="'protocol'"
|
||||
v-model="editForm.protocol"
|
||||
:placeholder="'mailto:'"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_contact_field_type_modal_protocol')"
|
||||
@submit="update"
|
||||
/>
|
||||
|
||||
<small class="form-text text-muted">
|
||||
{{ $t('settings.personalization_contact_field_type_modal_protocol_help') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-auto">
|
||||
<form-input
|
||||
:id="'icon'"
|
||||
v-model="editForm.icon"
|
||||
:placeholder="'fa fa-address-book-o'"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_contact_field_type_modal_icon')"
|
||||
@submit="update"
|
||||
/>
|
||||
|
||||
<small class="form-text text-muted">
|
||||
{{ $t('settings.personalization_contact_field_type_modal_icon_help') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeModal">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a class="btn btn-primary" href="" @click.prevent="update">
|
||||
{{ $t('app.edit') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
|
||||
<!-- Delete Contact field type -->
|
||||
<sweet-modal ref="modalDeleteContactFieldType" overlay-theme="dark"
|
||||
:title="$t('settings.personalization_contact_field_type_modal_delete_title')"
|
||||
>
|
||||
<p>
|
||||
{{ $t('settings.personalization_contact_field_type_modal_delete_description') }}
|
||||
</p>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeModal">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a class="btn btn-primary" href="" @click.prevent="trash">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
import Errors from '../partials/Error.vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
SweetModal,
|
||||
Errors,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
contactFieldTypes: [],
|
||||
|
||||
submitted: false,
|
||||
edited: false,
|
||||
deleted: false,
|
||||
|
||||
createForm: {
|
||||
name: '',
|
||||
protocol: '',
|
||||
icon: '',
|
||||
errors: []
|
||||
},
|
||||
|
||||
editForm: {
|
||||
id: '',
|
||||
name: '',
|
||||
protocol: '',
|
||||
icon: '',
|
||||
errors: []
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getContactFieldTypes();
|
||||
},
|
||||
|
||||
getContactFieldTypes() {
|
||||
axios.get('settings/personalization/contactfieldtypes')
|
||||
.then(response => {
|
||||
this.contactFieldTypes = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
add() {
|
||||
this.$refs.modalCreateContactFieldType.open();
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
this.$refs.modalCreateContactFieldType.close();
|
||||
this.$refs.modalEditContactFieldType.close();
|
||||
this.$refs.modalDeleteContactFieldType.close();
|
||||
},
|
||||
|
||||
store() {
|
||||
this.persistClient(
|
||||
'post', 'settings/personalization/contactfieldtypes',
|
||||
this.createForm, this.submitted
|
||||
);
|
||||
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('settings.personalization_contact_field_type_add_success'),
|
||||
text: '',
|
||||
width: '500px',
|
||||
type: 'success'
|
||||
});
|
||||
},
|
||||
|
||||
edit(contactFieldType) {
|
||||
this.editForm.id = contactFieldType.id;
|
||||
this.editForm.name = contactFieldType.name;
|
||||
this.editForm.protocol = contactFieldType.protocol;
|
||||
this.editForm.icon = contactFieldType.fontawesome_icon;
|
||||
|
||||
this.$refs.modalEditContactFieldType.open();
|
||||
},
|
||||
|
||||
update() {
|
||||
this.persistClient(
|
||||
'put', 'settings/personalization/contactfieldtypes/' + this.editForm.id,
|
||||
this.editForm, this.edited
|
||||
);
|
||||
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('settings.personalization_contact_field_type_edit_success'),
|
||||
text: '',
|
||||
width: '500px',
|
||||
type: 'success'
|
||||
});
|
||||
},
|
||||
|
||||
showDelete(contactFieldType) {
|
||||
this.editForm.id = contactFieldType.id;
|
||||
|
||||
this.$refs.modalDeleteContactFieldType.open();
|
||||
},
|
||||
|
||||
trash() {
|
||||
this.persistClient(
|
||||
'delete', 'settings/personalization/contactfieldtypes/' + this.editForm.id,
|
||||
this.editForm, this.deleted
|
||||
);
|
||||
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('settings.personalization_contact_field_type_delete_success'),
|
||||
text: '',
|
||||
width: '500px',
|
||||
type: 'success'
|
||||
});
|
||||
},
|
||||
|
||||
persistClient(method, uri, form, success) {
|
||||
form.errors = {};
|
||||
|
||||
axios[method](uri, form)
|
||||
.then(response => {
|
||||
this.getContactFieldTypes();
|
||||
|
||||
form.id = '';
|
||||
form.name = '';
|
||||
form.protocol = '';
|
||||
form.icon = '';
|
||||
form.errors = [];
|
||||
|
||||
this.closeModal();
|
||||
|
||||
success = true;
|
||||
})
|
||||
.catch(error => {
|
||||
if (typeof error.response.data === 'object') {
|
||||
form.errors = _.flatten(_.toArray(error.response.data));
|
||||
} else {
|
||||
form.errors = [this.$t('app.error_try_again')];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus on modal open.
|
||||
*/
|
||||
_focusCreateInput() {
|
||||
const vm = this;
|
||||
setTimeout(function() {
|
||||
vm.$refs.createName.focus();
|
||||
}, 10);
|
||||
},
|
||||
|
||||
/**
|
||||
* Focus on modal open.
|
||||
*/
|
||||
_focusEditInput() {
|
||||
const vm = this;
|
||||
setTimeout(function() {
|
||||
vm.$refs.editName.focus();
|
||||
}, 10);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
128
resources/js/components/settings/DAVResources.vue
Normal file
128
resources/js/components/settings/DAVResources.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<style scoped>
|
||||
.url {
|
||||
width: 650px;
|
||||
display: inline-block;
|
||||
margin-bottom: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2>{{ $t('settings.dav_title') }}</h2>
|
||||
<p>{{ $t('settings.dav_description') }}</p>
|
||||
|
||||
<notifications group="dav" position="top middle" :duration="5000" width="400" />
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dav_url_base">
|
||||
{{ $t('settings.dav_url_base') }}
|
||||
</label>
|
||||
<br />
|
||||
<input id="dav_url_base" :value="davRoute" class="url form-control" type="text" readonly />
|
||||
<a class="btn btn-primary" :title="$t('settings.dav_copy_help')" href="" @click.prevent="copyIntoClipboard(davRoute)">
|
||||
{{ $t('app.copy') }}
|
||||
</a>
|
||||
<p>
|
||||
{{ $t('settings.dav_connect_help') }}
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('settings.dav_connect_help2') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h2>{{ $t('settings.dav_title_carddav') }}</h2>
|
||||
<div class="form-group">
|
||||
<label for="dav_url_carddav">
|
||||
{{ $t('settings.dav_url_carddav') }}
|
||||
</label>
|
||||
<br />
|
||||
<input id="dav_url_carddav" :value="cardDavRoute" class="url form-control" type="text" readonly />
|
||||
<a class="btn btn-primary" :title="$t('settings.dav_copy_help')" href="" @click.prevent="copyIntoClipboard(cardDavRoute)">
|
||||
{{ $t('app.copy') }}
|
||||
</a>
|
||||
<br />
|
||||
<a :href="cardDavRoute+'?export'">
|
||||
{{ $t('settings.dav_carddav_export') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<h2>{{ $t('settings.dav_title_caldav') }}</h2>
|
||||
<div class="form-group">
|
||||
<label for="dav_url_caldav_birthdays">
|
||||
{{ $t('settings.dav_url_caldav_birthdays') }}
|
||||
</label>
|
||||
<br />
|
||||
<input id="dav_url_caldav_birthdays" :value="calDavBirthdaysRoute" class="url form-control" type="text" readonly />
|
||||
<a class="btn btn-primary" :title="$t('settings.dav_copy_help')" href="" @click.prevent="copyIntoClipboard(calDavBirthdaysRoute)">
|
||||
{{ $t('app.copy') }}
|
||||
</a>
|
||||
<br />
|
||||
<a :href="calDavBirthdaysRoute+'?export'">
|
||||
{{ $t('settings.dav_caldav_birthdays_export') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="dav_url_caldav_tasks">
|
||||
{{ $t('settings.dav_url_caldav_tasks') }}
|
||||
</label>
|
||||
<br />
|
||||
<input id="dav_url_caldav_tasks" :value="calDavTasksRoute" class="url form-control" type="text" readonly />
|
||||
<a class="btn btn-primary" :title="$t('settings.dav_copy_help')" href="" @click.prevent="copyIntoClipboard(calDavTasksRoute)">
|
||||
{{ $t('app.copy') }}
|
||||
</a>
|
||||
<br />
|
||||
<a :href="calDavTasksRoute+'?export'">
|
||||
{{ $t('settings.dav_caldav_tasks_export') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
davRoute: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
cardDavRoute: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
calDavBirthdaysRoute: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
calDavTasksRoute: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
copyIntoClipboard(text) {
|
||||
this.$copyText(text)
|
||||
.then(response => {
|
||||
this.notify(this.$t('settings.dav_clipboard_copied'), true);
|
||||
});
|
||||
},
|
||||
|
||||
notify(text, success) {
|
||||
this.$notify({
|
||||
group: 'dav',
|
||||
title: text,
|
||||
text: '',
|
||||
type: success ? 'success' : 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
443
resources/js/components/settings/Genders.vue
Normal file
443
resources/js/components/settings/Genders.vue
Normal file
@@ -0,0 +1,443 @@
|
||||
<template>
|
||||
<div>
|
||||
<notifications group="main" position="bottom right" />
|
||||
|
||||
<h3 class="mb3">
|
||||
{{ $t('settings.personalization_genders_title') }}
|
||||
<a class="btn nt2" :class="[ dirltr ? 'fr' : 'fl' ]" href="" @click.prevent="showCreateModal">
|
||||
{{ $t('settings.personalization_genders_add') }}
|
||||
</a>
|
||||
</h3>
|
||||
<p>{{ $t('settings.personalization_genders_desc') }}</p>
|
||||
|
||||
<div class="dt dt--fixed w-100 collapse br--top br--bottom">
|
||||
<div class="dt-row">
|
||||
<div class="dtc">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_genders_table_name') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_genders_table_sex') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_genders_table_default') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_contact_field_type_table_actions') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="gender in genders" :key="gender.id"
|
||||
class="dt-row bb b--light-gray"
|
||||
>
|
||||
<div class="dtc">
|
||||
<div class="pa2">
|
||||
{{ gender.name }}
|
||||
<span class="i">
|
||||
{{ $tc('settings.personalization_genders_list_contact_number', gender.numberOfContacts, { count: gender.numberOfContacts }) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc">
|
||||
<div class="pa2">
|
||||
{{ $t('settings.personalization_genders_' + gender.type.toLowerCase()) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc">
|
||||
<div class="pa2">
|
||||
<template v-if="gender.isDefault">
|
||||
{{ $t('settings.personalization_genders_default') }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2">
|
||||
<em class="fa fa-pencil-square-o pointer pr2" @click="showEdit(gender)"></em>
|
||||
<em v-if="genders.length > 1" class="fa fa-trash-o pointer" @click="showDelete(gender)"></em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt2" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<a class="pointer" href="" @click.prevent="showDefaultGenderModal">{{ $t('settings.personalization_genders_make_default') }}</a>
|
||||
</div>
|
||||
|
||||
<!-- Create Gender type -->
|
||||
<sweet-modal ref="createModal" overlay-theme="dark" :title="$t('settings.personalization_genders_modal_add')">
|
||||
<form @submit.prevent="store()">
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<form-input
|
||||
:id="''"
|
||||
v-model="createForm.name"
|
||||
:input-type="'text'"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_genders_modal_name')"
|
||||
/>
|
||||
<small class="form-text text-muted">
|
||||
{{ $t('settings.personalization_genders_modal_name_help') }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<form-select
|
||||
:id="''"
|
||||
v-model="createForm.type"
|
||||
:options="genderTypes"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_genders_modal_sex')"
|
||||
/>
|
||||
<small class="form-text text-muted">
|
||||
{{ $t('settings.personalization_genders_modal_sex_help') }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<form-toggle
|
||||
:id="''"
|
||||
v-model="createForm.isDefault"
|
||||
:labels="toggleOptions"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_genders_modal_default')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a class="btn btn-primary" href="" @click.prevent="store()">
|
||||
{{ $t('app.save') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
|
||||
<!-- Edit gender type -->
|
||||
<sweet-modal ref="updateModal" overlay-theme="dark" :title="$t('settings.personalization_genders_modal_edit')">
|
||||
<form @submit.prevent="update(updatedGender)">
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<form-input
|
||||
:id="''"
|
||||
v-model="updateForm.name"
|
||||
:input-type="'text'"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_genders_modal_name')"
|
||||
/>
|
||||
<small class="form-text text-muted">
|
||||
{{ $t('settings.personalization_genders_modal_name_help') }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<form-select
|
||||
:id="''"
|
||||
v-model="updateForm.type"
|
||||
:options="genderTypes"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_genders_modal_sex')"
|
||||
/>
|
||||
<small class="form-text text-muted">
|
||||
{{ $t('settings.personalization_genders_modal_sex_help') }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<form-toggle
|
||||
:id="''"
|
||||
v-model="updateForm.isDefault"
|
||||
:labels="toggleOptions"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_genders_modal_default')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeUpdateModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a class="btn btn-primary" href="" @click.prevent="update(updatedGender)">
|
||||
{{ $t('app.update') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
|
||||
<!-- Delete Gender type -->
|
||||
<sweet-modal ref="deleteModal" overlay-theme="dark" :title="$t('settings.personalization_genders_modal_delete')">
|
||||
<form>
|
||||
<div v-if="errorMessage !== ''" class="form-error-message mb3">
|
||||
<div class="pa2">
|
||||
<p class="mb0">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb4">
|
||||
<p class="mb2">
|
||||
{{ $t('settings.personalization_genders_modal_delete_desc', {name: deleteForm.name}) }}
|
||||
</p>
|
||||
<div v-if="deleteForm.numberOfContacts !== 0 || deleteForm.isDefault">
|
||||
<p v-if="deleteForm.numberOfContacts !== 0">
|
||||
{{ $tc('settings.personalization_genders_modal_delete_question', deleteForm.numberOfContacts, {count: deleteForm.numberOfContacts}) }}
|
||||
</p>
|
||||
<p v-else>
|
||||
{{ $tc('settings.personalization_genders_modal_delete_question_default') }}
|
||||
</p>
|
||||
<form-select
|
||||
:id="'deleteNewId'"
|
||||
v-model="deleteForm.newId"
|
||||
:options="genders"
|
||||
:required="true"
|
||||
:title="''"
|
||||
:excluded-id="deleteForm.id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeDeleteModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a v-if="deleteForm.numberOfContacts === 0 && ! deleteForm.isDefault"
|
||||
class="btn btn-primary"
|
||||
href=""
|
||||
@click.prevent="trash()"
|
||||
>
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
<a v-else class="btn btn-primary" href="" @click.prevent="trashAndReplace()">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
|
||||
<!-- Change default Gender -->
|
||||
<sweet-modal ref="defaultGenderModal" overlay-theme="dark" :title="$t('settings.personalization_genders_modal_default')">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<form-select
|
||||
:id="''"
|
||||
v-model="defaultGenderId"
|
||||
:options="genders"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_genders_select_default')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeDefaultGenderModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a class="btn btn-primary" href="" @click.prevent="updateDefaultGender()">
|
||||
{{ $t('app.save') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
SweetModal
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
genders: [],
|
||||
genderTypes: [],
|
||||
updatedGender: {
|
||||
id: '',
|
||||
name: '',
|
||||
type: '',
|
||||
isDefault: false,
|
||||
},
|
||||
|
||||
errorMessage: '',
|
||||
|
||||
createForm: {
|
||||
name: '',
|
||||
type: '',
|
||||
isDefault: false,
|
||||
errors: []
|
||||
},
|
||||
|
||||
updateForm: {
|
||||
id: '',
|
||||
name: '',
|
||||
type: '',
|
||||
isDefault: false,
|
||||
errors: []
|
||||
},
|
||||
|
||||
deleteForm: {
|
||||
id: '',
|
||||
name: '',
|
||||
isDefault: false,
|
||||
numberOfContacts: 0,
|
||||
newId: 0
|
||||
},
|
||||
|
||||
defaultGenderId: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
},
|
||||
|
||||
toggleOptions() {
|
||||
return {
|
||||
checked: this.$t('app.yes'),
|
||||
unchecked: this.$t('app.no')
|
||||
};
|
||||
},
|
||||
|
||||
defaultGenderType() {
|
||||
const defaultGender = _.findIndex(this.genders, ['isDefault', true]);
|
||||
return this.genders[defaultGender >= 0 ? defaultGender : 0];
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
axios.all([
|
||||
this.getGenders(),
|
||||
this.getGenderTypes()
|
||||
]);
|
||||
},
|
||||
|
||||
setDefaultGenderType() {
|
||||
this.createForm.type = this.defaultGenderType.type;
|
||||
},
|
||||
|
||||
getGenders() {
|
||||
return axios.get('settings/personalization/genders')
|
||||
.then(response => {
|
||||
this.genders = _.toArray(response.data);
|
||||
this.setDefaultGenderType();
|
||||
});
|
||||
},
|
||||
|
||||
getGenderTypes() {
|
||||
return axios.get('settings/personalization/genderTypes')
|
||||
.then(response => {
|
||||
this.genderTypes = _.toArray(response.data);
|
||||
});
|
||||
},
|
||||
|
||||
closeModal() {
|
||||
this.$refs.createModal.close();
|
||||
},
|
||||
|
||||
closeUpdateModal() {
|
||||
this.$refs.updateModal.close();
|
||||
},
|
||||
|
||||
closeDeleteModal() {
|
||||
this.$refs.deleteModal.close();
|
||||
},
|
||||
|
||||
closeDefaultGenderModal() {
|
||||
this.$refs.defaultGenderModal.close();
|
||||
},
|
||||
|
||||
showCreateModal() {
|
||||
this.$refs.createModal.open();
|
||||
},
|
||||
|
||||
showDefaultGenderModal() {
|
||||
this.defaultGenderId = this.defaultGenderType.id;
|
||||
this.$refs.defaultGenderModal.open();
|
||||
},
|
||||
|
||||
store() {
|
||||
axios.post('settings/personalization/genders', this.createForm)
|
||||
.then(response => {
|
||||
this.closeModal();
|
||||
this.getGenders();
|
||||
this.createForm.name = '';
|
||||
this.createForm.type = '';
|
||||
});
|
||||
},
|
||||
|
||||
showEdit(gender) {
|
||||
this.updateForm.id = gender.id.toString();
|
||||
this.updateForm.name = gender.name;
|
||||
this.updateForm.type = gender.type;
|
||||
this.updateForm.isDefault = gender.isDefault;
|
||||
this.updatedGender = gender;
|
||||
|
||||
this.$refs.updateModal.open();
|
||||
},
|
||||
|
||||
update() {
|
||||
axios.put('settings/personalization/genders/' + this.updateForm.id, this.updateForm)
|
||||
.then(response => {
|
||||
this.closeUpdateModal();
|
||||
this.getGenders();
|
||||
this.updatedGender.name = this.updateForm.name;
|
||||
this.updatedGender.type = this.updateForm.type;
|
||||
this.updatedGender.isDefault = this.updateForm.isDefault;
|
||||
this.updateForm.name = '';
|
||||
});
|
||||
},
|
||||
|
||||
showDelete(gender) {
|
||||
this.errorMessage = '';
|
||||
this.deleteForm.name = gender.name;
|
||||
this.deleteForm.id = gender.id.toString();
|
||||
this.deleteForm.isDefault = gender.isDefault;
|
||||
this.deleteForm.numberOfContacts = gender.numberOfContacts;
|
||||
|
||||
this.$refs.deleteModal.open();
|
||||
},
|
||||
|
||||
trash() {
|
||||
axios.delete('settings/personalization/genders/' + this.deleteForm.id)
|
||||
.then(response => {
|
||||
this.closeDeleteModal();
|
||||
this.getGenders();
|
||||
});
|
||||
},
|
||||
|
||||
trashAndReplace() {
|
||||
axios.delete('settings/personalization/genders/' + this.deleteForm.id + '/replaceby/' + this.deleteForm.newId)
|
||||
.then(response => {
|
||||
this.closeDeleteModal();
|
||||
this.getGenders();
|
||||
})
|
||||
.catch(error => {
|
||||
if (typeof error.response.data === 'object') {
|
||||
this.errorMessage = error.response.data.message;
|
||||
} else {
|
||||
this.errorMessage = this.$t('app.error_try_again');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateDefaultGender() {
|
||||
axios.put('settings/personalization/genders/default/' + this.defaultGenderId)
|
||||
.then(response => {
|
||||
this.closeDefaultGenderModal();
|
||||
this.getGenders();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
300
resources/js/components/settings/LifeEventTypes.vue
Normal file
300
resources/js/components/settings/LifeEventTypes.vue
Normal file
@@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<div>
|
||||
<notifications group="lifeEventTypes" position="bottom right" />
|
||||
|
||||
<h3 class="with-actions">
|
||||
{{ $t('settings.personalization_life_event_category_title') }}
|
||||
</h3>
|
||||
<p>{{ $t('settings.personalization_life_event_category_description') }}</p>
|
||||
|
||||
<div v-if="limited" class="mt3 mb3 form-information-message br2">
|
||||
<div class="pa3 flex">
|
||||
<div class="mr3">
|
||||
<svg viewBox="0 0 20 20">
|
||||
<g fill-rule="evenodd">
|
||||
<circle cx="10" cy="10" r="9" fill="currentColor" /><path d="M10 0C4.486 0 0 4.486 0 10s4.486 10 10 10 10-4.486 10-10S15.514 0 10 0m0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8m1-5v-3a1 1 0 0 0-1-1H9a1 1 0 1 0 0 2v3a1 1 0 0 0 1 1h1a1 1 0 1 0 0-2m-1-5.9a1.1 1.1 0 1 0 0-2.2 1.1 1.1 0 0 0 0 2.2" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div v-html="$t('settings.personalisation_paid_upgrade_vue', {url: 'settings/subscriptions' })"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dt dt--fixed w-100 collapse br--top br--bottom">
|
||||
<div class="dt-row">
|
||||
<div class="dtc">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_live_event_category_table_name') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_live_event_category_table_actions') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ul>
|
||||
<li v-for="lifeEventCategory in lifeEventCategories" :key="lifeEventCategory.id" class="dt dt--fixed w-100 collapse br--top br--bottom mt3">
|
||||
<!-- LIFE EVENT CATEGORY -->
|
||||
<div class="dt-row hover bb b--light-gray">
|
||||
<div class="dtc">
|
||||
<div class="pa2 b">
|
||||
<strong>{{ $t('people.life_event_category_' + lifeEventCategory.default_life_event_category_key) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc">
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="lifeEventType in lifeEventCategory.lifeEventTypes" :key="lifeEventType.id" class="dt-row hover bb b--light-gray">
|
||||
<div class="dtc">
|
||||
<div class="pa2 pl4">
|
||||
<template v-if="lifeEventType.name">
|
||||
{{ lifeEventType.name }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t('people.life_event_sentence_' + lifeEventType.default_life_event_type_key) }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2">
|
||||
<em v-if="!limited" class="fa fa-pencil-square-o pointer pr2" @click="showEditType(lifeEventType, lifeEventCategory.id)"></em>
|
||||
<em v-if="!limited" class="fa fa-trash-o pointer" @click="showDeleteType(lifeEventType)"></em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!limited" class="dt-row">
|
||||
<div class="dtc">
|
||||
<div class="pa2 pl4">
|
||||
<a class="pointer" href=""
|
||||
@click.prevent="showCreateType(lifeEventCategory)"
|
||||
>
|
||||
{{ $t('settings.personalization_life_event_type_add_button') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Create Life Event Type -->
|
||||
<sweet-modal ref="createTypeModal" overlay-theme="dark" :title="$t('settings.personalization_life_event_type_modal_add')">
|
||||
<form @submit.prevent="storeType()">
|
||||
<div class="mb4">
|
||||
<p class="b mb2"></p>
|
||||
<form-input
|
||||
:id="'add-type-name'"
|
||||
v-model="createTypeForm.name"
|
||||
:input-type="'text'"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_life_event_type_modal_question')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeCreateTypeModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a class="btn btn-primary" href="" @click.prevent="storeType()">
|
||||
{{ $t('app.save') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
|
||||
<!-- Update Life Event Type -->
|
||||
<sweet-modal ref="updateTypeModal" overlay-theme="dark" :title="$t('settings.personalization_life_event_type_modal_edit')">
|
||||
<form @submit.prevent="updateType()">
|
||||
<div class="mb4">
|
||||
<p class="b mb2"></p>
|
||||
<form-input
|
||||
:id="'update-type-name'"
|
||||
v-model="updateTypeForm.name"
|
||||
:input-type="'text'"
|
||||
:required="true"
|
||||
:title="$t('settings.personalization_life_event_type_modal_question')"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeUpdateTypeModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a class="btn btn-primary" href="" @click.prevent="updateType()">
|
||||
{{ $t('app.update') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
|
||||
<!-- Delete Life Event type -->
|
||||
<sweet-modal ref="deleteTypeModal" overlay-theme="dark" :title="$t('settings.personalization_life_event_type_modal_delete')">
|
||||
<form>
|
||||
<div v-if="errorMessage !== ''" class="form-error-message mb3">
|
||||
<div class="pa2">
|
||||
<p class="mb0">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb4">
|
||||
<p class="mb2">
|
||||
{{ $t('settings.personalization_life_event_type_modal_delete_desc') }}
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeDeleteTypeModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a class="btn btn-primary" href="" @click.prevent="destroyType()">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
SweetModal
|
||||
},
|
||||
|
||||
props: {
|
||||
limited: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
lifeEventCategories: [],
|
||||
errorMessage: '',
|
||||
|
||||
createTypeForm: {
|
||||
name: '',
|
||||
life_event_category_id: '',
|
||||
errors: []
|
||||
},
|
||||
|
||||
updateTypeForm: {
|
||||
id: '',
|
||||
name: '',
|
||||
life_event_category_id: '',
|
||||
errors: []
|
||||
},
|
||||
|
||||
destroyTypeForm: {
|
||||
id: '',
|
||||
errors: []
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getLifeEventCategories();
|
||||
},
|
||||
|
||||
getLifeEventCategories() {
|
||||
axios.get('settings/personalization/lifeeventcategories')
|
||||
.then(response => {
|
||||
this.lifeEventCategories = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
showCreateType(category) {
|
||||
this.$refs.createTypeModal.open();
|
||||
this.createTypeForm.life_event_category_id = category.id;
|
||||
},
|
||||
|
||||
showDeleteType(type) {
|
||||
this.destroyTypeForm.id = type.id;
|
||||
|
||||
this.$refs.deleteTypeModal.open();
|
||||
},
|
||||
|
||||
showEditType(type, categoryId) {
|
||||
this.updateTypeForm.id = type.id;
|
||||
this.updateTypeForm.name = type.name ? type.name : this.$t('people.life_event_sentence_' + type.default_life_event_type_key);
|
||||
this.updateTypeForm.life_event_category_id = categoryId;
|
||||
|
||||
this.$refs.updateTypeModal.open();
|
||||
},
|
||||
|
||||
closeCreateTypeModal() {
|
||||
this.$refs.createTypeModal.close();
|
||||
},
|
||||
|
||||
closeUpdateTypeModal() {
|
||||
this.$refs.updateTypeModal.close();
|
||||
},
|
||||
|
||||
closeDeleteTypeModal() {
|
||||
this.$refs.deleteTypeModal.close();
|
||||
},
|
||||
|
||||
storeType() {
|
||||
axios.post('settings/personalization/lifeeventtypes', this.createTypeForm)
|
||||
.then(response => {
|
||||
this.$refs.createTypeModal.close();
|
||||
this.createTypeForm.name = '';
|
||||
this.getLifeEventCategories();
|
||||
|
||||
this.notify(this.$t('app.default_save_success'), true);
|
||||
});
|
||||
},
|
||||
|
||||
updateType() {
|
||||
axios.put('settings/personalization/lifeeventtypes/' + this.updateTypeForm.id, this.updateTypeForm)
|
||||
.then(response => {
|
||||
this.$refs.updateTypeModal.close();
|
||||
this.updateTypeForm.name = '';
|
||||
this.getLifeEventCategories();
|
||||
|
||||
this.notify(this.$t('app.default_save_success'), true);
|
||||
});
|
||||
},
|
||||
|
||||
destroyType() {
|
||||
axios.delete('settings/personalization/lifeeventtypes/' + this.destroyTypeForm.id)
|
||||
.then(response => {
|
||||
this.$refs.deleteTypeModal.close();
|
||||
this.destroyTypeForm.id = '';
|
||||
this.getLifeEventCategories();
|
||||
|
||||
this.notify(this.$t('app.default_save_success'), true);
|
||||
})
|
||||
.catch(error => {
|
||||
this.errorMessage = error.response.data.message;
|
||||
});
|
||||
},
|
||||
|
||||
notify(text, success) {
|
||||
this.$notify({
|
||||
group: 'lifeEventTypes',
|
||||
title: text,
|
||||
text: '',
|
||||
type: success ? 'success' : 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
188
resources/js/components/settings/MfaActivate.vue
Normal file
188
resources/js/components/settings/MfaActivate.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<div>
|
||||
<notifications group="mfa" position="bottom right" :duration="5000" width="400" />
|
||||
|
||||
<h3>{{ $t('settings.2fa_otp_title') }}</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<a v-if="selectActivated" class="btn btn-warning" href="" @click.prevent="showDisableModal">
|
||||
{{ $t('settings.2fa_disable_title') }}
|
||||
</a>
|
||||
<a v-else class="btn btn-primary" href="" @click.prevent="showEnableModal">
|
||||
{{ $t('settings.2fa_enable_title') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<sweet-modal id="enableModal" ref="enableModal" overlay-theme="dark" :title="$t('settings.2fa_otp_title')">
|
||||
<form @submit.prevent="register()">
|
||||
<p>{{ $t('settings.2fa_enable_description') }}</p>
|
||||
|
||||
<div class="panel-body">
|
||||
{{ $t('settings.2fa_enable_otp') }}
|
||||
<div v-html="image"></div>
|
||||
<p>
|
||||
{{ $t('settings.2fa_enable_otp_help') }}
|
||||
<code id="secretkey">
|
||||
{{ secret }}
|
||||
</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<p>
|
||||
{{ $t('settings.2fa_enable_otp_validate') }}
|
||||
</p>
|
||||
<form-input
|
||||
:id="'one_time_password1'"
|
||||
v-model="one_time_password"
|
||||
:title="$t('auth.2fa_one_time_password')"
|
||||
:input-type="'number'"
|
||||
:width="100"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a id="verify1" class="btn btn-primary" href="" @click.prevent="register()">
|
||||
{{ $t('app.verify') }}
|
||||
</a>
|
||||
<a class="btn" href="" @click.prevent="closeEnableModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
|
||||
<sweet-modal id="disableModal" ref="disableModal" overlay-theme="dark" :title="$t('settings.2fa_otp_title')">
|
||||
<form @submit.prevent="register()">
|
||||
<p>{{ $t('settings.2fa_disable_description') }}</p>
|
||||
|
||||
<div class="form-group">
|
||||
<form-input
|
||||
:id="'one_time_password2'"
|
||||
v-model="one_time_password"
|
||||
:title="$t('auth.2fa_one_time_or_recuperation')"
|
||||
:input-type="'text'"
|
||||
:width="100"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a id="verify2" class="btn btn-primary" href="" @click.prevent="unregister()">
|
||||
{{ $t('app.verify') }}
|
||||
</a>
|
||||
<a class="btn" href="" @click.prevent="closeDisableModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
SweetModal
|
||||
},
|
||||
|
||||
props: {
|
||||
activated: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectActivated: false,
|
||||
errorMessage: '',
|
||||
infoMessage: '',
|
||||
success: false,
|
||||
one_time_password: '',
|
||||
image: '',
|
||||
secret: '',
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
activated: function (val) {
|
||||
this.selectActivated = val;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.selectActivated = this.activated;
|
||||
},
|
||||
|
||||
methods: {
|
||||
register() {
|
||||
axios.post('settings/security/2fa-enable', { one_time_password: this.one_time_password })
|
||||
.then(response => {
|
||||
this.closeEnableModal();
|
||||
this.selectActivated = response.data.success;
|
||||
if (response.data.success) {
|
||||
this.notify(this.$t('settings.2fa_enable_success'), true);
|
||||
} else {
|
||||
this.notify(this.$t('settings.2fa_enable_error'), false);
|
||||
}
|
||||
}).catch(error => {
|
||||
this.closeEnableModal();
|
||||
this.notify(error.response.data.message, false);
|
||||
});
|
||||
},
|
||||
|
||||
unregister() {
|
||||
axios.post('settings/security/2fa-disable', { one_time_password: this.one_time_password })
|
||||
.then(response => {
|
||||
this.closeDisableModal();
|
||||
this.selectActivated = ! response.data.success;
|
||||
if (response.data.success) {
|
||||
this.notify(this.$t('settings.2fa_disable_success'), true);
|
||||
} else {
|
||||
this.notify(this.$t('settings.2fa_disable_error'), false);
|
||||
}
|
||||
}).catch(error => {
|
||||
this.closeDisableModal();
|
||||
this.notify(error.response.data.message, false);
|
||||
});
|
||||
},
|
||||
|
||||
showEnableModal() {
|
||||
this.one_time_password = '';
|
||||
axios.get('settings/security/2fa-enable')
|
||||
.then(response => {
|
||||
this.image = response.data.image;
|
||||
this.secret = response.data.secret;
|
||||
this.$refs.enableModal.open();
|
||||
}).catch(error => {
|
||||
this.notify(error.response.data.message, false);
|
||||
});
|
||||
},
|
||||
|
||||
showDisableModal() {
|
||||
this.one_time_password = '';
|
||||
this.$refs.disableModal.open();
|
||||
},
|
||||
|
||||
closeEnableModal() {
|
||||
this.$refs.enableModal.close();
|
||||
},
|
||||
|
||||
closeDisableModal() {
|
||||
this.$refs.disableModal.close();
|
||||
},
|
||||
|
||||
notify(text, success) {
|
||||
this.$notify({
|
||||
group: 'mfa',
|
||||
title: text,
|
||||
text: '',
|
||||
type: success ? 'success' : 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
109
resources/js/components/settings/Modules.vue
Normal file
109
resources/js/components/settings/Modules.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="reminder-rules">
|
||||
<notifications group="main" position="bottom right" />
|
||||
|
||||
<h3 class="mb3">
|
||||
{{ $t('settings.personalization_module_title') }}
|
||||
</h3>
|
||||
<p>
|
||||
{{ $t('settings.personalization_module_desc') }}
|
||||
</p>
|
||||
|
||||
<div v-if="limited" class="mt3 mb3 form-information-message br2">
|
||||
<div class="pa3 flex">
|
||||
<div class="mr3">
|
||||
<svg viewBox="0 0 20 20">
|
||||
<g fill-rule="evenodd">
|
||||
<circle cx="10" cy="10" r="9" fill="currentColor" /><path d="M10 0C4.486 0 0 4.486 0 10s4.486 10 10 10 10-4.486 10-10S15.514 0 10 0m0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8m1-5v-3a1 1 0 0 0-1-1H9a1 1 0 1 0 0 2v3a1 1 0 0 0 1 1h1a1 1 0 1 0 0-2m-1-5.9a1.1 1.1 0 1 0 0-2.2 1.1 1.1 0 0 0 0 2.2" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div v-html="$t('settings.personalisation_paid_upgrade_vue', {url: 'settings/subscriptions' })"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dt dt--fixed w-100 collapse br--top br--bottom">
|
||||
<div class="dt-row">
|
||||
<div class="dtc">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_contact_field_type_table_name') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_contact_field_type_table_actions') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="module in modules" :key="module.id" class="dt-row hover bb b--light-gray">
|
||||
<div class="dtc">
|
||||
<div class="pa2">
|
||||
{{ module.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2">
|
||||
<form-toggle
|
||||
v-model="module.active"
|
||||
:iclass="'module-'"
|
||||
:disabled="limited"
|
||||
:labels="true"
|
||||
@change="toggle(module, $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
limited: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
modules: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getModules();
|
||||
},
|
||||
|
||||
methods: {
|
||||
getModules() {
|
||||
axios.get('settings/personalization/modules')
|
||||
.then(response => {
|
||||
this.modules = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
toggle(module) {
|
||||
axios.post('settings/personalization/modules/' + module.id)
|
||||
.then(response => {
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('settings.personalization_module_save'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
this.$set(module, 'active', response.data.data.active);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
139
resources/js/components/settings/RecoveryCodes.vue
Normal file
139
resources/js/components/settings/RecoveryCodes.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<style scoped>
|
||||
.code {
|
||||
margin-bottom: 0.1em;
|
||||
}
|
||||
.used {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3>{{ $t('settings.recovery_title') }}</h3>
|
||||
<div class="form-group">
|
||||
<a class="btn btn-primary" href="" @click.prevent="showRecoveryModal">
|
||||
{{ $t('settings.recovery_show') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<sweet-modal id="recoveryModal" ref="recoveryModal" overlay-theme="dark" :title="$t('settings.recovery_title')">
|
||||
<notifications group="recovery" position="top middle" :duration="5000" width="400" />
|
||||
|
||||
<p>{{ $t('settings.recovery_help_intro') }}</p>
|
||||
<p :class="[ dirltr ? 'ml3' : 'mr3' ]">
|
||||
<span v-for="code in codes" :key="code.id" v-cy-name="'recovery-' + code.id">
|
||||
<pre class="code" :class="[ code.used ? 'used' : '' ]" :title="[ code.used ? usedHelp : '']">{{ code.recovery }}</pre>
|
||||
</span>
|
||||
</p>
|
||||
<p>{{ $t('settings.recovery_help_information') }}</p>
|
||||
<div slot="button">
|
||||
<span :class="[ dirltr ? 'fl' : 'fr' ]">
|
||||
<a class="btn" href="" @click.prevent="generateNewCodes">
|
||||
{{ $t('settings.recovery_generate') }}
|
||||
</a>
|
||||
<br />
|
||||
<small class="form-text text-muted">
|
||||
{{ $t('settings.recovery_generate_help') }}
|
||||
</small>
|
||||
</span>
|
||||
<span :class="[ dirltr ? 'fr' : 'fl' ]">
|
||||
<a class="btn btn-primary" :title="copyHelp" href="" @click.prevent="copyIntoClipboard">
|
||||
{{ $t('app.copy') }}
|
||||
</a>
|
||||
<!--
|
||||
<a @click.prevent="download" class="btn" href="">{{ $t('app.download') }}</a>
|
||||
-->
|
||||
<a class="btn" href="" @click.prevent="closeRecoveryModal">
|
||||
{{ $t('app.close') }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
SweetModal
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
codes: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
},
|
||||
usedHelp() {
|
||||
return this.$t('settings.recovery_already_used_help');
|
||||
},
|
||||
copyHelp() {
|
||||
return this.$t('settings.recovery_copy_help');
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
showRecoveryModal() {
|
||||
this.codes = [];
|
||||
axios.post('settings/security/recovery-codes')
|
||||
.then(response => {
|
||||
this.codes = response.data;
|
||||
this.$refs.recoveryModal.open();
|
||||
}).catch(error => {
|
||||
this.notify(error.response.data.message, false);
|
||||
});
|
||||
},
|
||||
|
||||
generateNewCodes() {
|
||||
this.codes = [];
|
||||
axios.post('settings/security/generate-recovery-codes')
|
||||
.then(response => {
|
||||
this.codes = response.data;
|
||||
}).catch(error => {
|
||||
this.notify(error.response.data.message, false);
|
||||
});
|
||||
},
|
||||
|
||||
closeRecoveryModal() {
|
||||
this.$refs.recoveryModal.close();
|
||||
},
|
||||
|
||||
copyIntoClipboard() {
|
||||
this.$copyText(this.getDataStream())
|
||||
.then(response => {
|
||||
this.notify(this.$t('settings.recovery_clipboard'), true);
|
||||
});
|
||||
},
|
||||
|
||||
getDataStream() {
|
||||
var text = this.$t('settings.recovery_help_intro')+'\n';
|
||||
var i = 1;
|
||||
this.codes.forEach(code => {
|
||||
if (code.used) {
|
||||
text += i + '. ---------\n';
|
||||
} else {
|
||||
text += i + '. ' + code.recovery + '\n';
|
||||
}
|
||||
i++;
|
||||
});
|
||||
return text;
|
||||
},
|
||||
|
||||
notify(text, success) {
|
||||
this.$notify({
|
||||
group: 'recovery',
|
||||
title: text,
|
||||
text: '',
|
||||
type: success ? 'success' : 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
92
resources/js/components/settings/ReminderRules.vue
Normal file
92
resources/js/components/settings/ReminderRules.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="reminder-rules">
|
||||
<notifications group="main" position="bottom right" />
|
||||
|
||||
<h3 class="mb3">
|
||||
{{ $t('settings.personalization_reminder_rule_title') }}
|
||||
</h3>
|
||||
<p>
|
||||
{{ $t('settings.personalization_reminder_rule_desc') }}
|
||||
</p>
|
||||
|
||||
<div class="dt dt--fixed w-100 collapse br--top br--bottom">
|
||||
<div class="dt-row">
|
||||
<div class="dtc">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_contact_field_type_table_name') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2 b">
|
||||
{{ $t('settings.personalization_contact_field_type_table_actions') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="reminderRule in reminderRules" :key="reminderRule.id" class="dt-row bb b--light-gray">
|
||||
<div class="dtc">
|
||||
<div class="pa2">
|
||||
{{ $tc('settings.personalization_reminder_rule_line', reminderRule.number_of_days_before, {count: reminderRule.number_of_days_before}) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtc" :class="[ dirltr ? 'tr' : 'tl' ]">
|
||||
<div class="pa2">
|
||||
<form-toggle
|
||||
v-model="reminderRule.active"
|
||||
:iclass="'reminder-rule-' + reminderRule.number_of_days_before"
|
||||
:labels="true"
|
||||
@change="toggle(reminderRule, $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
reminderRules: [],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
dirltr() {
|
||||
return this.$root.htmldir === 'ltr';
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.getReminderRules();
|
||||
},
|
||||
|
||||
getReminderRules() {
|
||||
axios.get('settings/personalization/reminderrules')
|
||||
.then(response => {
|
||||
this.reminderRules = response.data;
|
||||
});
|
||||
},
|
||||
|
||||
toggle(reminderRule, event) {
|
||||
axios.post('settings/personalization/reminderrules/' + reminderRule.id)
|
||||
.then(response => {
|
||||
this.$notify({
|
||||
group: 'main',
|
||||
title: this.$t('settings.personalization_reminder_rule_save'),
|
||||
text: '',
|
||||
type: 'success'
|
||||
});
|
||||
this.$set(reminderRule, 'active', response.data.data.active);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
108
resources/js/components/settings/ReminderTime.vue
Normal file
108
resources/js/components/settings/ReminderTime.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Timezone -->
|
||||
<div class="form-group">
|
||||
<form-select
|
||||
:id="'timezone'"
|
||||
:value="updatedTimezone"
|
||||
:options="timezones"
|
||||
:title="$t('settings.timezone')"
|
||||
:required="true"
|
||||
:iclass="'form-control'"
|
||||
@input="timezoneUpdate"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Reminders -->
|
||||
<div class="form-group">
|
||||
<form-select
|
||||
:id="'reminder_time'"
|
||||
:value="updatedReminder"
|
||||
:options="hours"
|
||||
:title="$t('settings.reminder_time_to_send')"
|
||||
:required="true"
|
||||
:iclass="'form-control'"
|
||||
@input="reminderUpdate"
|
||||
/>
|
||||
<small class="form-text text-muted" v-html="message"></small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
timezone: {
|
||||
type: String,
|
||||
default: 'UTC',
|
||||
},
|
||||
timezones: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
reminder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
hours: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
message: '',
|
||||
updatedTimezone: '',
|
||||
updatedReminder: ''
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.updatedReminder = this.reminder;
|
||||
this.updatedTimezone = this.timezone;
|
||||
this.computeMessage();
|
||||
},
|
||||
|
||||
timezoneUpdate: function(event) {
|
||||
this.updatedTimezone = event;
|
||||
this.computeMessage();
|
||||
},
|
||||
|
||||
reminderUpdate: function(event) {
|
||||
this.updatedReminder = event;
|
||||
this.computeMessage();
|
||||
},
|
||||
|
||||
computeMessage() {
|
||||
var moment = require('moment-timezone');
|
||||
moment.locale(this._i18n.locale);
|
||||
moment.tz.setDefault('UTC');
|
||||
|
||||
var now = moment();
|
||||
var t = now.format('YYYY-MM-DD ' + this.updatedReminder + ':00');
|
||||
|
||||
var date = moment.tz(t, this.updatedTimezone);
|
||||
|
||||
if (date.isBefore(now)) {
|
||||
date = date.add(1, 'days');
|
||||
}
|
||||
|
||||
this.message = this.$t('settings.reminder_time_to_send_help', {
|
||||
dateTime: date.format('LLL'),
|
||||
dateTimeUtc: date.utc().format('YYYY-MM-DD HH:mm z')
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
297
resources/js/components/settings/Subscription.vue
Normal file
297
resources/js/components/settings/Subscription.vue
Normal file
@@ -0,0 +1,297 @@
|
||||
<template>
|
||||
<form ref="form" class="mb4" :action="callback" method="post" @submit.prevent="subscribe()">
|
||||
<notifications group="subscription" position="top middle" :duration="5000" width="400" />
|
||||
|
||||
<div class="form-group">
|
||||
<div v-if="errors" role="alert" class="alert alert-danger w-100">
|
||||
{{ errors }}
|
||||
</div>
|
||||
|
||||
<div v-if="paymentSucceeded">
|
||||
<h1 class="text-xl mt-2 mb-4 text-gray-700">
|
||||
{{ $t('settings.subscriptions_payment_succeeded_title') }}
|
||||
</h1>
|
||||
<p v-if="successMessage" class="mb-6">
|
||||
{{ successMessage }}
|
||||
</p>
|
||||
<p v-else class="mb-6">
|
||||
{{ $t('settings.subscriptions_payment_succeeded') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="paymentCancelled">
|
||||
<h1 class="text-xl mt-2 mb-4 text-gray-700">
|
||||
{{ $t('settings.subscriptions_payment_cancelled_title') }}
|
||||
</h1>
|
||||
|
||||
<p class="mb-6">
|
||||
{{ $t('settings.subscriptions_payment_cancelled') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="! paymentProcessed" id="payment-elements" class="b--gray-monica ba pa4 br2 mb3 bg-black-05">
|
||||
<div class="form-row">
|
||||
<div class="mb3">
|
||||
<form-input
|
||||
:id="'cardholder-name'"
|
||||
v-model="selectedName"
|
||||
:input-type="'text'"
|
||||
:iclass="'br3 b--black-30 ba pa3 w-100 f4'"
|
||||
:required="true"
|
||||
:title="$t('settings.subscriptions_upgrade_name')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb3">
|
||||
<form-input
|
||||
:id="'address-zip'"
|
||||
v-model="zip"
|
||||
:input-type="'text'"
|
||||
:iclass="'br3 b--black-30 ba pa3 w-100 f4'"
|
||||
:title="$t('settings.subscriptions_upgrade_zip')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label for="card-element">
|
||||
{{ $t('settings.subscriptions_upgrade_credit') }}
|
||||
</label>
|
||||
<div id="card-element">
|
||||
<!-- a Stripe Element will be inserted here. -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
id="card-button"
|
||||
class="btn btn-primary w-100 mt3"
|
||||
:disabled="paymentProcessing"
|
||||
@click.prevent="confirm ? confirmPayment() : subscribe()"
|
||||
v-html="$t('settings.subscriptions_upgrade_submit', { amount: amount })"
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
<a v-if="paymentProcessed" :href="callback"
|
||||
class="btn btn-secondary w-100 tc"
|
||||
>
|
||||
{{ $t('app.go_back') }}
|
||||
</a>
|
||||
</div>
|
||||
<input type="hidden" name="_token" :value="token" />
|
||||
<input type="hidden" name="plan" :value="plan" />
|
||||
<input type="hidden" name="payment_method" :value="paymentMethod" />
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
stripeKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
clientSecret: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
plan: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
amount: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
callback: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
token: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
confirm: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
paymentSucceeded: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
paymentCancelled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedName: '',
|
||||
stripe: null,
|
||||
zip: '',
|
||||
errors: '',
|
||||
successMessage: '',
|
||||
cardElement: null,
|
||||
paymentMethod: '',
|
||||
paymentProcessing: false,
|
||||
paymentProcessed: false,
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
name() {
|
||||
this.selectedName = this.name;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.selectedName = this.name;
|
||||
if (this.paymentSucceeded || this.paymentCancelled) {
|
||||
this.paymentProcessed = true;
|
||||
}
|
||||
if (! this.paymentProcessed) {
|
||||
this.start();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
start() {
|
||||
this.stripe = Stripe(this.stripeKey);
|
||||
|
||||
const elements = this.stripe.elements();
|
||||
|
||||
// Custom styling can be passed to options when creating an Element.
|
||||
// (Note that this demo uses a wider set of styles than the guide below.)
|
||||
const style = {
|
||||
base: {
|
||||
color: '#32325d',
|
||||
lineHeight: '18px',
|
||||
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
|
||||
fontSmoothing: 'antialiased',
|
||||
fontSize: '16px',
|
||||
'::placeholder': {
|
||||
color: '#aab7c4'
|
||||
}
|
||||
},
|
||||
invalid: {
|
||||
color: '#fa755a',
|
||||
iconColor: '#fa755a'
|
||||
}
|
||||
};
|
||||
|
||||
// Create an instance of the card Element
|
||||
this.cardElement = elements.create('card', {
|
||||
hidePostalCode: true,
|
||||
style: style
|
||||
});
|
||||
|
||||
// Add an instance of the card Element into the `card-element` <div>
|
||||
this.cardElement.mount('#card-element');
|
||||
|
||||
// Handle real-time validation errors from the card Element.
|
||||
var self = this;
|
||||
this.cardElement.addEventListener('change', function(event) {
|
||||
if (event.error) {
|
||||
self.errors = event.error.message;
|
||||
} else {
|
||||
self.errors = '';
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handleError(error) {
|
||||
if (error.code === 'parameter_invalid_empty' &&
|
||||
error.param === 'payment_method_data[billing_details][name]') {
|
||||
this.errors = this.$t('settings.subscriptions_payment_error_name');
|
||||
} else {
|
||||
this.errors = error.message;
|
||||
}
|
||||
},
|
||||
|
||||
subscribe() {
|
||||
var self = this;
|
||||
|
||||
this.errors = '';
|
||||
this.paymentProcessing = true;
|
||||
this.paymentProcessed = false;
|
||||
|
||||
this.stripe.handleCardSetup(
|
||||
self.clientSecret,
|
||||
self.cardElement,
|
||||
{
|
||||
payment_method_data: {
|
||||
billing_details: {
|
||||
name: self.selectedName,
|
||||
address: {
|
||||
postal_code: self.zip,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
).then(function (result) {
|
||||
self.paymentProcessing = false;
|
||||
if (result.error) {
|
||||
self.handleError(result.error);
|
||||
} else {
|
||||
// The card has been verified successfully...
|
||||
self.paymentProcessed = true;
|
||||
self.paymentSucceeded = true;
|
||||
self.successMessage = self.$t('settings.subscriptions_payment_success');
|
||||
self.notify(self.successMessage, true);
|
||||
self.processPayment(result.setupIntent);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
processPayment(setupIntent) {
|
||||
var self = this;
|
||||
this.paymentMethod = setupIntent.payment_method;
|
||||
setTimeout(function () {
|
||||
self.$refs.form.submit();
|
||||
}, 10);
|
||||
},
|
||||
|
||||
confirmPayment() {
|
||||
var self = this;
|
||||
|
||||
this.paymentProcessing = true;
|
||||
this.paymentProcessed = false;
|
||||
this.errorMessage = '';
|
||||
|
||||
this.stripe.handleCardPayment(
|
||||
self.clientSecret, self.cardElement, {
|
||||
payment_method_data: {
|
||||
billing_details: { name: this.selectedName }
|
||||
}
|
||||
}
|
||||
).then(function (result) {
|
||||
self.paymentProcessing = false;
|
||||
|
||||
if (result.error) {
|
||||
self.handleError(result.error);
|
||||
} else {
|
||||
self.paymentProcessed = true;
|
||||
self.paymentSucceeded = true;
|
||||
self.successMessage = self.$t('settings.subscriptions_payment_success');
|
||||
self.notify(self.successMessage, true);
|
||||
setTimeout(function () {
|
||||
window.location = self.callback;
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
notify(text, success) {
|
||||
this.$notify({
|
||||
group: 'subscription',
|
||||
title: text,
|
||||
text: '',
|
||||
type: success ? 'success' : 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
402
resources/js/components/settings/WebauthnConnector.vue
Normal file
402
resources/js/components/settings/WebauthnConnector.vue
Normal file
@@ -0,0 +1,402 @@
|
||||
<style scoped>
|
||||
.time {
|
||||
color: gray;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="form-group">
|
||||
<notifications group="webauthn" position="top middle" :duration="5000" width="400" />
|
||||
|
||||
<div v-if="method === 'register-modal'">
|
||||
<h3>{{ $t('settings.webauthn_title') }}</h3>
|
||||
|
||||
<div v-if="currentkeys !== null">
|
||||
<ul class="table">
|
||||
<li v-for="key in currentkeys"
|
||||
:key="key.id"
|
||||
class="table-row"
|
||||
>
|
||||
<div class="table-cell w-30">
|
||||
<strong>{{ key.name }}</strong>
|
||||
</div>
|
||||
<div class="table-cell time w-50">
|
||||
<template v-if="key.counter > 0">
|
||||
{{ $t('settings.webauthn_last_use', {timestamp: formatTime(key.updated_at)}) }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="table-cell actions">
|
||||
<a class="pointer" href="" @click.prevent="showDeleteModal(key.id)">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<a v-if="isSupported" class="btn btn-primary" href="" @click.prevent="showRegisterModal">
|
||||
{{ $t('settings.webauthn_enable_description') }}
|
||||
</a>
|
||||
<small v-else>
|
||||
{{ notSupportedMessage() }}
|
||||
</small>
|
||||
|
||||
|
||||
<sweet-modal
|
||||
id="registerModal"
|
||||
ref="registerModal"
|
||||
overlay-theme="dark"
|
||||
:title="$t('settings.webauthn_title')"
|
||||
>
|
||||
<div v-if="registerTab === '1'">
|
||||
<p>
|
||||
{{ $t('settings.webauthn_key_name_help') }}
|
||||
</p>
|
||||
<form-input
|
||||
:id="'keyName'"
|
||||
v-model="keyName"
|
||||
:title="$t('settings.webauthn_key_name')"
|
||||
:value="keyName"
|
||||
:input-type="'text'"
|
||||
:width="150"
|
||||
:required="true"
|
||||
@keyup.enter="showRegisterModalTab('2');startRegister();"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="registerTab === '2'">
|
||||
<div v-if="errorMessage !== ''" class="form-error-message mb3">
|
||||
<div class="pa2">
|
||||
<p class="mb0">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
<p>
|
||||
<a href="" @click.prevent="startRegister()">
|
||||
{{ $t('app.retry') }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="infoMessage !== ''" class="form-information-message mb3">
|
||||
<div class="pa2">
|
||||
<p class="mb0">
|
||||
{{ infoMessage }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="errorMessage === ''" class="tc">
|
||||
<img src="https://ssl.gstatic.com/accounts/strongauth/Challenge_2SV-Gnubby_graphic.png"
|
||||
:alt="$t('settings.webauthn_insertKey')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="errorMessage === ''" class="pa2">
|
||||
<p>
|
||||
{{ $t('settings.webauthn_insertKey') }}
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('settings.webauthn_buttonAdvise') }}
|
||||
<br />
|
||||
{{ $t('settings.webauthn_noButtonAdvise') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="button">
|
||||
<a v-if="registerTab === '1'" class="btn" href="" @click.prevent="showRegisterModalTab('2');startRegister();">
|
||||
{{ $t('pagination.next') }}
|
||||
</a>
|
||||
<a v-else class="btn" href="" @click.prevent="showRegisterModalTab('1')">
|
||||
{{ $t('pagination.previous') }}
|
||||
</a>
|
||||
<a class="btn" href="" @click.prevent="closeRegisterModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="errorMessage !== ''" class="form-error-message mb3">
|
||||
<div class="pa2">
|
||||
<p class="mb0">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
<p>
|
||||
<a href="" @click.prevent="start()">
|
||||
{{ $t('app.retry') }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="infoMessage !== ''" class="form-information-message mb3">
|
||||
<div class="pa2">
|
||||
<p class="mb0">
|
||||
{{ infoMessage }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tc">
|
||||
<img src="https://ssl.gstatic.com/accounts/strongauth/Challenge_2SV-Gnubby_graphic.png"
|
||||
:alt="$t('settings.webauthn_insertKey')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="pa2">
|
||||
<p>
|
||||
{{ $t('settings.webauthn_insertKey') }}
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('settings.webauthn_buttonAdvise') }}
|
||||
<br />
|
||||
{{ $t('settings.webauthn_noButtonAdvise') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sweet-modal ref="delete" overlay-theme="dark" title="Remove a key">
|
||||
<form>
|
||||
<div class="mb4">
|
||||
{{ $t('settings.webauthn_delete_confirmation') }}
|
||||
</div>
|
||||
</form>
|
||||
<div slot="button">
|
||||
<a class="btn" href="" @click.prevent="closeDeleteModal()">
|
||||
{{ $t('app.cancel') }}
|
||||
</a>
|
||||
<a class="btn" href="" @click.prevent="webauthnRemove(keyToTrash)">
|
||||
{{ $t('app.delete') }}
|
||||
</a>
|
||||
</div>
|
||||
</sweet-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { SweetModal } from 'sweet-modal-vue';
|
||||
import * as WebAuthn from '../../../../vendor/asbiin/laravel-webauthn/resources/js/webauthn.js';
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
SweetModal
|
||||
},
|
||||
|
||||
props: {
|
||||
keys: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
publicKey: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
method: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
timezone: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
script: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isSupported: true,
|
||||
errorMessage: '',
|
||||
infoMessage: '',
|
||||
success: false,
|
||||
currentkeys: [],
|
||||
keyToTrash: '',
|
||||
keyName: '',
|
||||
registerTab: '',
|
||||
data: null,
|
||||
webauthn: null,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.prepareComponent();
|
||||
this.start();
|
||||
},
|
||||
|
||||
methods: {
|
||||
prepareComponent() {
|
||||
this.currentkeys = this.keys;
|
||||
this.data = this.registerdata;
|
||||
|
||||
this.webauthn = new WebAuthn((name, message) => {
|
||||
this.errorMessage = this._errorMessage(name, message);
|
||||
});
|
||||
},
|
||||
|
||||
_errorMessage(name, message) {
|
||||
switch (name) {
|
||||
case 'InvalidStateError':
|
||||
return this.$t('settings.webauthn_error_already_used');
|
||||
case 'NotAllowedError':
|
||||
return this.$t('settings.webauthn_error_not_allowed');
|
||||
default:
|
||||
return message;
|
||||
}
|
||||
},
|
||||
|
||||
notSupportedMessage() {
|
||||
return this.$t('settings.webauthn_'+this.webauthn.notSupportedMessage());
|
||||
},
|
||||
|
||||
start() {
|
||||
var self = this;
|
||||
this.errorMessage = '';
|
||||
|
||||
if (! this.webauthn.webAuthnSupport()) {
|
||||
this.isSupported = false;
|
||||
this.errorMessage = this.notSupportedMessage();
|
||||
}
|
||||
|
||||
switch(this.method) {
|
||||
case 'register':
|
||||
setTimeout(function () {
|
||||
self.webauthn.register(
|
||||
self.publicKey,
|
||||
function (datas) { self.webauthnRegisterCallback(datas, true); }
|
||||
);
|
||||
}, 10);
|
||||
break;
|
||||
case 'login':
|
||||
this.webauthn.sign(
|
||||
this.publicKey,
|
||||
function (data) { self.webauthnLoginCallback(data); }
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
showRegisterModal() {
|
||||
this.errorMessage = '';
|
||||
this.infoMessage = '';
|
||||
this.keyName = '';
|
||||
this.success = false;
|
||||
this.showRegisterModalTab('1');
|
||||
this.$refs.registerModal.open();
|
||||
},
|
||||
|
||||
showRegisterModalTab(tab) {
|
||||
this.registerTab = tab;
|
||||
},
|
||||
|
||||
startRegister() {
|
||||
var self = this;
|
||||
this.errorMessage = '';
|
||||
axios.post('webauthn/keys/options')
|
||||
.then(response => {
|
||||
if (self.registerTab === '2') {
|
||||
var data = response.data.publicKey;
|
||||
setTimeout(function () {
|
||||
self.webauthn.register(
|
||||
data,
|
||||
function (datas) { self.webauthnRegisterCallback(datas, false); }
|
||||
);
|
||||
}, 10);
|
||||
}
|
||||
}).catch(error => {
|
||||
self.notify(error.response.data.message, false);
|
||||
});
|
||||
},
|
||||
|
||||
closeRegisterModal() {
|
||||
this.$refs.registerModal.close();
|
||||
this.showRegisterModalTab('');
|
||||
},
|
||||
|
||||
webauthnRegisterCallback(data, redirect) {
|
||||
var self = this;
|
||||
axios.post('webauthn/keys', {
|
||||
...data,
|
||||
name: self.keyName,
|
||||
}).then(response => {
|
||||
self.success = true;
|
||||
self.notify(self.$t('settings.webauthn_success'), true);
|
||||
self.currentkeys.push({
|
||||
id: response.result.id,
|
||||
name: response.result.name,
|
||||
});
|
||||
}).then(response => {
|
||||
if (redirect) {
|
||||
setTimeout(function () {
|
||||
window.location = response.data.callback;
|
||||
}, 100);
|
||||
} else {
|
||||
self.closeRegisterModal();
|
||||
}
|
||||
}).catch(error => {
|
||||
self.errorMessage = error.message ? error.message : error.response.data.message;
|
||||
});
|
||||
},
|
||||
|
||||
webauthnLoginCallback(data) {
|
||||
var self = this;
|
||||
axios.post('webauthn/auth', {
|
||||
...data
|
||||
}).then(response => {
|
||||
self.success = true;
|
||||
self.notify(self.$t('settings.webauthn_success'), true);
|
||||
|
||||
window.location = response.data.callback;
|
||||
}).catch(error => {
|
||||
self.errorMessage = error.message ? error.message : error.response.data.message;
|
||||
});
|
||||
},
|
||||
|
||||
webauthnRemove(id) {
|
||||
var self = this;
|
||||
axios.delete('webauthn/keys/'+id)
|
||||
.then(response => {
|
||||
self.currentkeys.splice(self.currentkeys.indexOf(self.currentkeys.find(item => item.id === response.data.id)), 1);
|
||||
self.success = true;
|
||||
self.notify(self.$t('settings.webauthn_delete_success'), true);
|
||||
self.closeDeleteModal();
|
||||
}).catch(error => {
|
||||
self.errorMessage = error.response.data.message;
|
||||
});
|
||||
},
|
||||
|
||||
showDeleteModal(id) {
|
||||
this.keyToTrash = id;
|
||||
this.$refs.delete.open();
|
||||
},
|
||||
|
||||
closeDeleteModal() {
|
||||
this.$refs.delete.close();
|
||||
},
|
||||
|
||||
formatTime(value) {
|
||||
var moment = require('moment-timezone');
|
||||
moment.locale(this._i18n.locale);
|
||||
moment.tz.setDefault('UTC');
|
||||
|
||||
var t = moment(value);
|
||||
var date = moment.tz(t, this.timezone);
|
||||
|
||||
return date.format('LLLL');
|
||||
},
|
||||
|
||||
notify(text, success) {
|
||||
this.$notify({
|
||||
group: 'webauthn',
|
||||
title: text,
|
||||
text: '',
|
||||
type: success ? 'success' : 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user