refactor: replace custom CRM with Monica fork
Some checks failed
Build & Push Monica Image to Gitea Registry / build-and-push (push) Failing after 9s

This commit is contained in:
Kroonk
2026-05-22 16:19:55 +02:00
parent 38cc4c09ca
commit a213a1dba0
2215 changed files with 242567 additions and 6015 deletions

View 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>

View 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>

View 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>