mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-03 05:43:24 +00:00
Fixes for Todo's (#1208)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
78b1d38340
commit
1ee8f0f0fe
@ -14,6 +14,7 @@
|
|||||||
"docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/tool staging",
|
"docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/tool staging",
|
||||||
"docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/tool",
|
"docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/tool",
|
||||||
"run-local": "cross-env MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_ENDPOINT=localhost MONGO_URL=mongodb://localhost:27017 TRANSACTOR_URL=ws:/localhost:3333 TELEGRAM_DATABASE=telegram-service ELASTIC_URL=http://localhost:9200 REKONI_URL=http://localhost:4004 ts-node ./src/index.ts",
|
"run-local": "cross-env MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_ENDPOINT=localhost MONGO_URL=mongodb://localhost:27017 TRANSACTOR_URL=ws:/localhost:3333 TELEGRAM_DATABASE=telegram-service ELASTIC_URL=http://localhost:9200 REKONI_URL=http://localhost:4004 ts-node ./src/index.ts",
|
||||||
|
"upgrade": "rushx run-local upgrade",
|
||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"format": "prettier --write src && eslint --fix src"
|
"format": "prettier --write src && eslint --fix src"
|
||||||
},
|
},
|
||||||
|
@ -288,7 +288,10 @@ export function createModel (builder: Builder): void {
|
|||||||
|
|
||||||
const applicantKanbanLookup: Lookup<Applicant> = {
|
const applicantKanbanLookup: Lookup<Applicant> = {
|
||||||
attachedTo: recruit.mixin.Candidate,
|
attachedTo: recruit.mixin.Candidate,
|
||||||
assignee: contact.class.Employee
|
assignee: contact.class.Employee,
|
||||||
|
_id: {
|
||||||
|
todoItems: task.class.TodoItem
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||||
|
@ -334,9 +334,12 @@ export function createModel (builder: Builder): void {
|
|||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
options: {
|
options: {
|
||||||
lookup: {
|
lookup: {
|
||||||
assignee: contact.class.Employee
|
assignee: contact.class.Employee,
|
||||||
|
_id: {
|
||||||
|
todoItems: task.class.TodoItem
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} as FindOptions<Doc>, // TODO: fix
|
} as FindOptions<Doc>,
|
||||||
config: []
|
config: []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -69,10 +69,9 @@
|
|||||||
{#if show}<Close size={'small'} />{:else}<Calendar size={'medium'} />{/if}
|
{#if show}<Close size={'small'} />{:else}<Calendar size={'medium'} />{/if}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="group">
|
<div class="group">
|
||||||
<span class="label"><Label label={title} /></span>
|
<span class="label"><Label label={title} /></span>
|
||||||
{#if value !== undefined}
|
{#if value != null}
|
||||||
<DatePresenter {value} withTime={withTime} {bigDay} wraped={opened} />
|
<DatePresenter {value} withTime={withTime} {bigDay} wraped={opened} />
|
||||||
{:else}
|
{:else}
|
||||||
<span class="result not-selected"><Label label={ui.string.NotSelected} /></span>
|
<span class="result not-selected"><Label label={ui.string.NotSelected} /></span>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { afterUpdate, onDestroy } from 'svelte'
|
import { afterUpdate, onDestroy } from 'svelte'
|
||||||
import { tooltipstore as tooltip, closeTooltip } from '..'
|
import { tooltipstore as tooltip, closeTooltip, Component } from '..'
|
||||||
import type { TooltipAligment } from '..'
|
import type { TooltipAligment } from '..'
|
||||||
import Label from './Label.svelte'
|
import Label from './Label.svelte'
|
||||||
|
|
||||||
@ -163,7 +163,11 @@
|
|||||||
{#if $tooltip.label}<div class="fs-title mb-4">
|
{#if $tooltip.label}<div class="fs-title mb-4">
|
||||||
<Label label={$tooltip.label} params={$tooltip.props ?? {}} />
|
<Label label={$tooltip.label} params={$tooltip.props ?? {}} />
|
||||||
</div>{/if}
|
</div>{/if}
|
||||||
<svelte:component this={$tooltip.component} {...$tooltip.props} />
|
{#if typeof $tooltip.component === 'string'}
|
||||||
|
<Component is={$tooltip.component} props={$tooltip.props}/>
|
||||||
|
{:else}
|
||||||
|
<svelte:component this={$tooltip.component} {...$tooltip.props} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div bind:this={nubHTML} class="nub {nubDirection ?? ''}" />
|
<div bind:this={nubHTML} class="nub {nubDirection ?? ''}" />
|
||||||
{:else if $tooltip.label}
|
{:else if $tooltip.label}
|
||||||
|
@ -17,12 +17,13 @@
|
|||||||
import { CommentsPresenter } from '@anticrm/chunter-resources'
|
import { CommentsPresenter } from '@anticrm/chunter-resources'
|
||||||
import { formatName } from '@anticrm/contact'
|
import { formatName } from '@anticrm/contact'
|
||||||
import type { WithLookup } from '@anticrm/core'
|
import type { WithLookup } from '@anticrm/core'
|
||||||
|
import notification from '@anticrm/notification'
|
||||||
import { Avatar } from '@anticrm/presentation'
|
import { Avatar } from '@anticrm/presentation'
|
||||||
import type { Applicant } from '@anticrm/recruit'
|
import type { Applicant } from '@anticrm/recruit'
|
||||||
import { ActionIcon, Component, IconMoreH, showPanel } from '@anticrm/ui'
|
import task, { TodoItem } from '@anticrm/task'
|
||||||
|
import { ActionIcon, Component, IconMoreH, showPanel, Tooltip } from '@anticrm/ui'
|
||||||
import view from '@anticrm/view'
|
import view from '@anticrm/view'
|
||||||
import ApplicationPresenter from './ApplicationPresenter.svelte'
|
import ApplicationPresenter from './ApplicationPresenter.svelte'
|
||||||
import notification from '@anticrm/notification'
|
|
||||||
|
|
||||||
export let object: WithLookup<Applicant>
|
export let object: WithLookup<Applicant>
|
||||||
export let draggable: boolean
|
export let draggable: boolean
|
||||||
@ -30,6 +31,9 @@
|
|||||||
function showCandidate () {
|
function showCandidate () {
|
||||||
showPanel(view.component.EditDoc, object.attachedTo, object.attachedToClass, 'full')
|
showPanel(view.component.EditDoc, object.attachedTo, object.attachedToClass, 'full')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: todoItems = (object.$lookup?.todoItems as TodoItem[]) ?? []
|
||||||
|
$: doneTasks = todoItems.filter((it) => it.done)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card-container" {draggable} class:draggable on:dragstart on:dragend>
|
<div class="card-container" {draggable} class:draggable on:dragstart on:dragend>
|
||||||
@ -38,7 +42,7 @@
|
|||||||
<Avatar avatar={object.$lookup?.attachedTo?.avatar} size={'medium'} />
|
<Avatar avatar={object.$lookup?.attachedTo?.avatar} size={'medium'} />
|
||||||
<div class="flex-grow flex-col min-w-0 ml-2">
|
<div class="flex-grow flex-col min-w-0 ml-2">
|
||||||
<div class="fs-title over-underline lines-limit-2" on:click={showCandidate}>
|
<div class="fs-title over-underline lines-limit-2" on:click={showCandidate}>
|
||||||
{formatName(object.$lookup?.attachedTo?.name)}
|
{formatName(object.$lookup?.attachedTo?.name ?? '')}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm lines-limit-2">{object.$lookup?.attachedTo?.title ?? ''}</div>
|
<div class="text-sm lines-limit-2">{object.$lookup?.attachedTo?.title ?? ''}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -54,6 +58,13 @@
|
|||||||
<div class="flex-row-center">
|
<div class="flex-row-center">
|
||||||
<div class="sm-tool-icon step-lr75">
|
<div class="sm-tool-icon step-lr75">
|
||||||
<ApplicationPresenter value={object} />
|
<ApplicationPresenter value={object} />
|
||||||
|
{#if todoItems.length > 0}
|
||||||
|
<Tooltip label={task.string.TodoItems} component={task.component.TodoItemsPopup} props={{ value: object }}>
|
||||||
|
<div class="ml-2">
|
||||||
|
( {doneTasks?.length}/ {todoItems.length} )
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if (object.attachments ?? 0) > 0}
|
{#if (object.attachments ?? 0) > 0}
|
||||||
<div class="step-lr75"><AttachmentsPresenter value={object} /></div>
|
<div class="step-lr75"><AttachmentsPresenter value={object} /></div>
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"CantStatusDelete": "Can't delete status",
|
"CantStatusDelete": "Can't delete status",
|
||||||
"CantStatusDeleteError": "There are objects in the given state. Move or delete them first.",
|
"CantStatusDeleteError": "There are objects in the given state. Move or delete them first.",
|
||||||
"Tasks": "Tasks",
|
"Tasks": "Tasks",
|
||||||
"Assigned": "Assigned to me"
|
"Assigned": "Assigned to me",
|
||||||
|
"TodoItems": "Todos"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -74,6 +74,7 @@
|
|||||||
"CantStatusDelete": "Невозможно удалить статус",
|
"CantStatusDelete": "Невозможно удалить статус",
|
||||||
"CantStatusDeleteError": "Есть объекты с данным статусом. Сначала переместите или удалите их. ",
|
"CantStatusDeleteError": "Есть объекты с данным статусом. Сначала переместите или удалите их. ",
|
||||||
"Tasks": "Задачи",
|
"Tasks": "Задачи",
|
||||||
"Assigned": "Назначения"
|
"Assigned": "Назначения",
|
||||||
|
"TodoItems": "Todos"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,33 +13,52 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { CommentsPresenter } from '@anticrm/chunter-resources'
|
|
||||||
import { AttachmentsPresenter } from '@anticrm/attachment-resources'
|
import { AttachmentsPresenter } from '@anticrm/attachment-resources'
|
||||||
|
import { CommentsPresenter } from '@anticrm/chunter-resources'
|
||||||
import type { WithLookup } from '@anticrm/core'
|
import type { WithLookup } from '@anticrm/core'
|
||||||
|
import notification from '@anticrm/notification'
|
||||||
import { Avatar } from '@anticrm/presentation'
|
import { Avatar } from '@anticrm/presentation'
|
||||||
import type { Issue } from '@anticrm/task'
|
import type { Issue, TodoItem } from '@anticrm/task'
|
||||||
import { ActionIcon, Component, IconMoreH, showPopup } from '@anticrm/ui'
|
import { ActionIcon, Component, IconMoreH, showPopup, Tooltip } from '@anticrm/ui'
|
||||||
import { ContextMenu } from '@anticrm/view-resources'
|
import { ContextMenu } from '@anticrm/view-resources'
|
||||||
import task from '../plugin'
|
import task from '../plugin'
|
||||||
import TaskPresenter from './TaskPresenter.svelte'
|
import TaskPresenter from './TaskPresenter.svelte'
|
||||||
import notification from '@anticrm/notification'
|
|
||||||
|
|
||||||
export let object: WithLookup<Issue>
|
export let object: WithLookup<Issue>
|
||||||
export let draggable: boolean
|
export let draggable: boolean
|
||||||
|
|
||||||
const showMenu = (ev?: Event): void => {
|
const showMenu = (ev?: Event): void => {
|
||||||
showPopup(ContextMenu, { object }, ev ? ev.target as HTMLElement : null)
|
showPopup(ContextMenu, { object }, ev ? (ev.target as HTMLElement) : null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: todoItems = (object.$lookup?.todoItems as TodoItem[]) ?? []
|
||||||
|
$: doneTasks = todoItems.filter((it) => it.done)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card-container" {draggable} class:draggable on:dragstart on:dragend>
|
<div class="card-container" {draggable} class:draggable on:dragstart on:dragend>
|
||||||
<div class="flex-between mb-2">
|
<div class="flex-between mb-2">
|
||||||
<TaskPresenter value={object} />
|
<div class="flex">
|
||||||
|
<TaskPresenter value={object} />
|
||||||
|
{#if todoItems.length > 0}
|
||||||
|
<Tooltip label={task.string.TodoItems} component={task.component.TodoItemsPopup} props={{ value: object }}>
|
||||||
|
<div class="ml-2">
|
||||||
|
( {doneTasks?.length}/ {todoItems.length} )
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
<div class="flex-row-center">
|
<div class="flex-row-center">
|
||||||
<div class="mr-2">
|
<div class="mr-2">
|
||||||
<Component is={notification.component.NotificationPresenter} props={{ value: object }} />
|
<Component is={notification.component.NotificationPresenter} props={{ value: object }} />
|
||||||
</div>
|
</div>
|
||||||
<ActionIcon label={task.string.More} action={(evt) => { showMenu(evt) }} icon={IconMoreH} size={'small'} />
|
<ActionIcon
|
||||||
|
label={task.string.More}
|
||||||
|
action={(evt) => {
|
||||||
|
showMenu(evt)
|
||||||
|
}}
|
||||||
|
icon={IconMoreH}
|
||||||
|
size={'small'}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="caption-color mb-3 lines-limit-4">{object.name}</div>
|
<div class="caption-color mb-3 lines-limit-4">{object.name}</div>
|
||||||
@ -54,7 +73,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Avatar avatar={object.$lookup?.assignee?.avatar} size={'x-small'} />
|
<Avatar avatar={object.$lookup?.assignee?.avatar} size={'x-small'} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@ -62,10 +81,12 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 1rem 1.25rem;
|
padding: 1rem 1.25rem;
|
||||||
background-color: rgba(222, 222, 240, .06);
|
background-color: rgba(222, 222, 240, 0.06);
|
||||||
border-radius: .75rem;
|
border-radius: 0.75rem;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&.draggable { cursor: grab; }
|
&.draggable {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
export let value: State
|
export let value: State
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if value}
|
{#if value}
|
||||||
<div class="overflow-label state-container" style="background-color: {getPlatformColor(value.color)}">
|
<div class="overflow-label state-container" style="background-color: {getPlatformColor(value.color)}">
|
||||||
{value.title}
|
{value.title}
|
||||||
|
@ -24,15 +24,18 @@
|
|||||||
export let item: TodoItem
|
export let item: TodoItem
|
||||||
|
|
||||||
let name: string = ''
|
let name: string = ''
|
||||||
let dueTo: Date | undefined
|
let dueTo: Date | undefined | null = null
|
||||||
|
|
||||||
let _itemId: Ref<TodoItem>
|
let _itemId: Ref<TodoItem>
|
||||||
|
|
||||||
$: if (_itemId !== item._id) {
|
$: if (_itemId !== item._id) {
|
||||||
_itemId = item._id
|
_itemId = item._id
|
||||||
name = item.name
|
name = item.name
|
||||||
dueTo = new Date(item.dueTo ?? 0)
|
if (item.dueTo != null) {
|
||||||
console.log('AHTUNG', item, dueTo)
|
dueTo = new Date(item.dueTo)
|
||||||
|
} else {
|
||||||
|
dueTo = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2020 Anticrm Platform Contributors.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import type { WithLookup } from '@anticrm/core'
|
||||||
|
import type { Task, TodoItem } from '@anticrm/task'
|
||||||
|
import task from '@anticrm/task'
|
||||||
|
import { Table } from '@anticrm/view-resources'
|
||||||
|
import plugin from '../../plugin'
|
||||||
|
|
||||||
|
export let value: WithLookup<Task>
|
||||||
|
|
||||||
|
$: todos = (value.$lookup?.todoItems as TodoItem[]) ?? []
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="applications-container">
|
||||||
|
{#if todos.length > 0}
|
||||||
|
<Table
|
||||||
|
_class={task.class.TodoItem}
|
||||||
|
config={[
|
||||||
|
{ key: 'name', label: plugin.string.TodoName },
|
||||||
|
'dueTo',
|
||||||
|
{ key: 'done', presenter: plugin.component.TodoStatePresenter, label: plugin.string.TodoState }
|
||||||
|
]}
|
||||||
|
options={{}}
|
||||||
|
query={{ attachedTo: value._id }}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.applications-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--theme-caption-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -15,17 +15,15 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { TodoItem } from '@anticrm/task'
|
import { getPlatformColor, Label } from '@anticrm/ui'
|
||||||
import { Label } from '@anticrm/ui'
|
|
||||||
import task from '../../plugin'
|
import task from '../../plugin'
|
||||||
export let value: TodoItem
|
export let value: boolean
|
||||||
|
|
||||||
$: color = value.done ? '#60B96E' : '#6F7BC5'
|
$: color = value ? getPlatformColor(2) : getPlatformColor(10)
|
||||||
$: text = value.done ? task.string.DoneState : task.string.UndoneState
|
$: text = value ? task.string.DoneState : task.string.UndoneState
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
{#if value !== undefined }
|
||||||
{#if value }
|
|
||||||
<div class="overflow-label state-container" style="background-color: {color};">
|
<div class="overflow-label state-container" style="background-color: {color};">
|
||||||
<Label label={text}/>
|
<Label label={text}/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,6 +37,7 @@ import TaskItem from './components/TaskItem.svelte'
|
|||||||
import TaskPresenter from './components/TaskPresenter.svelte'
|
import TaskPresenter from './components/TaskPresenter.svelte'
|
||||||
import TemplatesIcon from './components/TemplatesIcon.svelte'
|
import TemplatesIcon from './components/TemplatesIcon.svelte'
|
||||||
import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte'
|
import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte'
|
||||||
|
import TodoItemsPopup from './components/todos/TodoItemsPopup.svelte'
|
||||||
import Todos from './components/todos/Todos.svelte'
|
import Todos from './components/todos/Todos.svelte'
|
||||||
import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte'
|
import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte'
|
||||||
import AssignedTasks from './components/AssignedTasks.svelte'
|
import AssignedTasks from './components/AssignedTasks.svelte'
|
||||||
@ -147,7 +148,8 @@ export default async (): Promise<Resources> => ({
|
|||||||
DoneStateEditor,
|
DoneStateEditor,
|
||||||
KanbanTemplateEditor,
|
KanbanTemplateEditor,
|
||||||
KanbanTemplateSelector,
|
KanbanTemplateSelector,
|
||||||
AssignedTasks
|
AssignedTasks,
|
||||||
|
TodoItemsPopup
|
||||||
},
|
},
|
||||||
actionImpl: {
|
actionImpl: {
|
||||||
CreateTask: createTask,
|
CreateTask: createTask,
|
||||||
|
@ -227,7 +227,11 @@ const task = plugin(taskId, {
|
|||||||
},
|
},
|
||||||
component: {
|
component: {
|
||||||
KanbanTemplateEditor: '' as AnyComponent,
|
KanbanTemplateEditor: '' as AnyComponent,
|
||||||
KanbanTemplateSelector: '' as AnyComponent
|
KanbanTemplateSelector: '' as AnyComponent,
|
||||||
|
TodoItemsPopup: '' as AnyComponent
|
||||||
|
},
|
||||||
|
string: {
|
||||||
|
TodoItems: '' as IntlString
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user