From fab8ee5f144790a9e07ddc2fe7e8392062769180 Mon Sep 17 00:00:00 2001
From: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
Date: Tue, 29 Mar 2022 20:33:39 +0600
Subject: [PATCH] Applications view (#1237)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
---
 models/recruit/src/index.ts                   | 16 +++-
 models/recruit/src/plugin.ts                  |  3 +-
 .../src/components/ApplicationsView.svelte    | 90 +++++++++++++++++++
 plugins/recruit-resources/src/index.ts        |  4 +-
 4 files changed, 109 insertions(+), 4 deletions(-)
 create mode 100644 plugins/recruit-resources/src/components/ApplicationsView.svelte

diff --git a/models/recruit/src/index.ts b/models/recruit/src/index.ts
index f8e9d934ad..8dcb08204c 100644
--- a/models/recruit/src/index.ts
+++ b/models/recruit/src/index.ts
@@ -30,6 +30,7 @@ import {
   UX
 } from '@anticrm/model'
 import attachment from '@anticrm/model-attachment'
+import calendar from '@anticrm/model-calendar'
 import chunter from '@anticrm/model-chunter'
 import contact, { TPerson } from '@anticrm/model-contact'
 import core, { TSpace } from '@anticrm/model-core'
@@ -39,10 +40,9 @@ import task, { TSpaceWithStates, TTask } from '@anticrm/model-task'
 import view from '@anticrm/model-view'
 import workbench from '@anticrm/model-workbench'
 import { Applicant, Candidate, Candidates, Vacancy } from '@anticrm/recruit'
-import { TOpinion, TReview, TReviewCategory } from './review-model'
 import recruit from './plugin'
 import { createReviewModel, reviewTableConfig, reviewTableOptions } from './review'
-import calendar from '@anticrm/model-calendar'
+import { TOpinion, TReview, TReviewCategory } from './review-model'
 
 @Model(recruit.class.Vacancy, task.class.SpaceWithStates)
 @UX(recruit.string.Vacancy, recruit.icon.Vacancy)
@@ -106,6 +106,10 @@ export class TApplicant extends TTask implements Applicant {
   @Prop(TypeRef(recruit.mixin.Candidate), recruit.string.Candidate)
   declare attachedTo: Ref<Candidate>
 
+  // We need to declare, to provide property with label
+  @Prop(TypeRef(recruit.class.Vacancy), recruit.string.Vacancy)
+  declare space: Ref<Vacancy>
+
   @Prop(Collection(attachment.class.Attachment), attachment.string.Attachments)
   attachments?: number
 
@@ -160,6 +164,14 @@ export function createModel (builder: Builder): void {
             createItemLabel: recruit.string.VacancyCreateLabel,
             position: 'bottom'
           },
+          {
+            id: 'applicants',
+            component: recruit.component.ApplicationsView,
+            icon: recruit.icon.Application,
+            label: recruit.string.Applications,
+            createItemLabel: recruit.string.ApplicationCreateLabel,
+            position: 'bottom'
+          },
           {
             id: 'candidates',
             component: recruit.component.Candidates,
diff --git a/models/recruit/src/plugin.ts b/models/recruit/src/plugin.ts
index a86fe6da22..cdcf5b30dd 100644
--- a/models/recruit/src/plugin.ts
+++ b/models/recruit/src/plugin.ts
@@ -77,7 +77,8 @@ export default mergeIds(recruitId, recruit, {
     EditReview: '' as AnyComponent,
     ReviewPresenter: '' as AnyComponent,
     Opinions: '' as AnyComponent,
-    OpinionPresenter: '' as AnyComponent
+    OpinionPresenter: '' as AnyComponent,
+    ApplicationsView: '' as AnyComponent
   },
   template: {
     DefaultVacancy: '' as Ref<KanbanTemplate>,
diff --git a/plugins/recruit-resources/src/components/ApplicationsView.svelte b/plugins/recruit-resources/src/components/ApplicationsView.svelte
new file mode 100644
index 0000000000..be8a857228
--- /dev/null
+++ b/plugins/recruit-resources/src/components/ApplicationsView.svelte
@@ -0,0 +1,90 @@
+<!--
+// Copyright © 2020, 2021 Anticrm Platform Contributors.
+// Copyright © 2021 Hardcore Engineering Inc.
+// 
+// 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 attachment from '@anticrm/attachment'
+  import chunter from '@anticrm/chunter'
+  import contact from '@anticrm/contact'
+  import core,{ Doc,DocumentQuery,FindOptions } from '@anticrm/core'
+  import { Applicant } from '@anticrm/recruit'
+  import task from '@anticrm/task'
+  import { Button,Icon,IconAdd,Label,Scroller,SearchEdit,showPopup } from '@anticrm/ui'
+import { BuildModelKey } from '@anticrm/view';
+  import { Table } from '@anticrm/view-resources'
+  import recruit from '../plugin'
+  import CreateApplication from './CreateApplication.svelte'
+
+  let search = ''
+  let resultQuery: DocumentQuery<Doc> = {}
+  const baseQuery: DocumentQuery<Applicant> = {
+    doneState: null
+  }
+
+  const config: (BuildModelKey | string)[] = [
+    '',
+    '$lookup.space',
+    '$lookup.attachedTo',
+    '$lookup.assignee',
+    '$lookup.state',
+    {
+      key: '',
+      presenter: attachment.component.AttachmentsPresenter,
+      label: attachment.string.Files,
+      sortingKey: 'attachments'
+    },
+    { key: '', presenter: chunter.component.CommentsPresenter, label: chunter.string.Comments, sortingKey: 'comments' },
+    'modifiedOn',
+    '$lookup.attachedTo.$lookup.channels'
+  ]
+
+  const options: FindOptions<Applicant> = {
+    lookup: {
+      attachedTo: [recruit.mixin.Candidate, { _id: { channels: contact.class.Channel } }],
+      state: task.class.State,
+      assignee: contact.class.Employee,
+      space: recruit.class.Vacancy
+    }
+  }
+
+  function showCreateDialog (ev: Event) {
+    showPopup(CreateApplication, { }, ev.target as HTMLElement)
+  }
+
+  function updateResultQuery (search: string): void {
+    resultQuery = (search === '') ? baseQuery : { ...baseQuery,$search: search }
+  }
+</script>
+
+<div class="ac-header full">
+  <div class="ac-header__wrap-title">
+    <div class="ac-header__icon"><Icon icon={recruit.icon.Application} size={'small'} /></div>
+    <span class="ac-header__title"><Label label={recruit.string.Applications} /></span>
+  </div>
+
+  <SearchEdit bind:value={search} on:change={() => { updateResultQuery(search) }} />
+  <Button icon={IconAdd} label={recruit.string.ApplicationCreateLabel} primary size={'small'} on:click={(ev) => showCreateDialog(ev)} />
+</div>
+
+<Scroller>
+  <Table
+    _class={recruit.class.Applicant}
+    {config}
+    {options}
+    query={ resultQuery }
+    showNotification
+    highlightRows
+  />
+</Scroller>
diff --git a/plugins/recruit-resources/src/index.ts b/plugins/recruit-resources/src/index.ts
index 6b4e598862..957a58c82c 100644
--- a/plugins/recruit-resources/src/index.ts
+++ b/plugins/recruit-resources/src/index.ts
@@ -48,6 +48,7 @@ import VacancyPresenter from './components/VacancyPresenter.svelte'
 import VacancyCountPresenter from './components/VacancyCountPresenter.svelte'
 import VacancyModifiedPresenter from './components/VacancyModifiedPresenter.svelte'
 import ReviewCategoryPresenter from './components/review/ReviewCategoryPresenter.svelte'
+import ApplicationsView from './components/ApplicationsView.svelte'
 import recruit from './plugin'
 
 async function createApplication (object: Doc): Promise<void> {
@@ -166,7 +167,8 @@ export default async (): Promise<Resources> => ({
     Opinions,
     OpinionPresenter,
     OpinionsPresenter,
-    ReviewCategoryPresenter
+    ReviewCategoryPresenter,
+    ApplicationsView
   },
   completion: {
     ApplicationQuery: async (client: Client, query: string) => await queryApplication(client, query)