mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-30 04:05:39 +00:00
UBER-148: My Applications in recruit (#3235)
Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
This commit is contained in:
parent
8b6e304ceb
commit
e164e1c9af
@ -240,7 +240,7 @@ export function createModel (builder: Builder): void {
|
||||
const skillsId = 'skills'
|
||||
const candidatesId = 'candidates'
|
||||
const archiveId = 'archive'
|
||||
const assignedId = 'assigned'
|
||||
const myApplicationsId = 'my-applications'
|
||||
const organizationsId = 'organizations'
|
||||
|
||||
builder.createDoc(
|
||||
@ -324,8 +324,8 @@ export function createModel (builder: Builder): void {
|
||||
position: 'bottom'
|
||||
},
|
||||
{
|
||||
id: assignedId,
|
||||
label: task.string.AssignedToMe,
|
||||
id: myApplicationsId,
|
||||
label: recruit.string.MyApplications,
|
||||
icon: recruit.icon.AssignedToMe,
|
||||
component: task.component.AssignedTasks,
|
||||
position: 'event',
|
||||
@ -941,7 +941,7 @@ export function createModel (builder: Builder): void {
|
||||
createGotoSpecialAction(builder, talentsId, 'g->e', recruit.string.GotoTalents)
|
||||
createGotoSpecialAction(builder, vacanciesId, 'g->v', recruit.string.GotoVacancies)
|
||||
createGotoSpecialAction(builder, skillsId, 'g->s', recruit.string.GotoSkills)
|
||||
createGotoSpecialAction(builder, assignedId, 'g->h', recruit.string.GotoAssigned)
|
||||
createGotoSpecialAction(builder, myApplicationsId, 'g->h', recruit.string.GotoMyApplications)
|
||||
createGotoSpecialAction(builder, candidatesId, 'g->a', recruit.string.GotoApplicants)
|
||||
|
||||
createAction(builder, {
|
||||
|
@ -58,7 +58,7 @@ export default mergeIds(recruitId, recruit, {
|
||||
GotoTalents: '' as IntlString,
|
||||
GotoVacancies: '' as IntlString,
|
||||
GotoSkills: '' as IntlString,
|
||||
GotoAssigned: '' as IntlString,
|
||||
GotoMyApplications: '' as IntlString,
|
||||
GotoApplicants: '' as IntlString,
|
||||
GotoRecruitApplication: '' as IntlString,
|
||||
VacancyList: '' as IntlString,
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { TabList } from '@hcengineering/ui'
|
||||
import TabList from './TabList.svelte'
|
||||
import { IModeSelector } from '../utils'
|
||||
|
||||
export let props: IModeSelector
|
@ -181,6 +181,7 @@ export { default as Wizard } from './components/wizard/Wizard.svelte'
|
||||
export { default as StepsDialog } from './components/StepsDialog.svelte'
|
||||
export { default as EmojiPopup } from './components/EmojiPopup.svelte'
|
||||
export { default as IconWithEmojii } from './components/IconWithEmojii.svelte'
|
||||
export { default as ModeSelector } from './components/ModeSelector.svelte'
|
||||
|
||||
export * from './types'
|
||||
export * from './location'
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
import { generateId } from '@hcengineering/core'
|
||||
import type { Metadata } from '@hcengineering/platform'
|
||||
import { setMetadata } from '@hcengineering/platform'
|
||||
import { IntlString, setMetadata } from '@hcengineering/platform'
|
||||
import autolinker from 'autolinker'
|
||||
import { writable } from 'svelte/store'
|
||||
import { Notification, NotificationPosition, NotificationSeverity, notificationsStore } from '.'
|
||||
@ -169,3 +169,12 @@ export function replaceURLs (text: string): string {
|
||||
stripPrefix: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface IModeSelector {
|
||||
mode: string
|
||||
config: Array<[string, IntlString, object]>
|
||||
onChange: (_mode: string) => void
|
||||
}
|
||||
|
@ -84,7 +84,7 @@
|
||||
"GotoTalents": "Go to Talents",
|
||||
"GotoVacancies": "Go to Vacancies",
|
||||
"GotoSkills": "Go to Skills",
|
||||
"GotoAssigned": "Go to my Assigned",
|
||||
"GotoMyApplications": "Go to My Applications",
|
||||
"GotoApplicants": "Go to Applications",
|
||||
"GotoRecruitApplication": "Switch to Recruit Application",
|
||||
"AddDropHere": "Add or drop resume",
|
||||
@ -112,7 +112,8 @@
|
||||
"OpenVacancyList": "Open list",
|
||||
"Export": "Export",
|
||||
"ConfigLabel": "Recruiting",
|
||||
"ConfigDescription": "Extension to manage Talents/Applicants and Vacancies."
|
||||
"ConfigDescription": "Extension to manage Talents/Applicants and Vacancies.",
|
||||
"MyApplications": "My applications"
|
||||
},
|
||||
"status": {
|
||||
"TalentRequired": "Please select talent",
|
||||
|
@ -86,7 +86,7 @@
|
||||
"GotoTalents": "Перейте к талантам",
|
||||
"GotoVacancies": "Перейти к вакансиям",
|
||||
"GotoSkills": "Перейти к навыкам",
|
||||
"GotoAssigned": "Перейти к моим назначениям",
|
||||
"GotoMyApplications": "Перейти к Моим Кандидатам",
|
||||
"GotoApplicants": "Перейти к кандидатам",
|
||||
"GotoRecruitApplication": "Перейти к Приложению Рекрутинг",
|
||||
"AddDropHere": "Добавить или перетянуть резюме",
|
||||
@ -112,7 +112,8 @@
|
||||
"OpenVacancyList": "Открыть список",
|
||||
"Export": "Экспорт",
|
||||
"ConfigLabel": "Рекрутинг",
|
||||
"ConfigDescription": "Модуль по работе с талантами/кандидатами и вакансиями"
|
||||
"ConfigDescription": "Модуль по работе с талантами/кандидатами и вакансиями",
|
||||
"MyApplications": "Мои кандидаты"
|
||||
},
|
||||
"status": {
|
||||
"TalentRequired": "Пожалуйста выберите таланта",
|
||||
|
@ -124,6 +124,7 @@ export default mergeIds(recruitId, recruit, {
|
||||
PerformMatch: '' as IntlString,
|
||||
MoveApplication: '' as IntlString,
|
||||
Application: '' as IntlString,
|
||||
MyApplications: '' as IntlString,
|
||||
|
||||
TemplateReplace: '' as IntlString,
|
||||
TemplateReplaceConfirm: '' as IntlString,
|
||||
|
@ -14,29 +14,53 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { EmployeeAccount } from '@hcengineering/contact'
|
||||
import { Class, DocumentQuery, getCurrentAccount, Ref } from '@hcengineering/core'
|
||||
import { Class, Doc, DocumentQuery, getCurrentAccount, Ref, WithLookup } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import tags, { selectedTagElements, TagCategory, TagElement } from '@hcengineering/tags'
|
||||
import { DoneState, Task } from '@hcengineering/task'
|
||||
import { Component, Label, SearchEdit } from '@hcengineering/ui'
|
||||
import { TableBrowser } from '@hcengineering/view-resources'
|
||||
import { Component, IModeSelector, Label, resolvedLocationStore, SearchEdit, ModeSelector } from '@hcengineering/ui'
|
||||
import {
|
||||
activeViewlet,
|
||||
FilterButton,
|
||||
getViewOptions,
|
||||
makeViewletKey,
|
||||
TableBrowser,
|
||||
updateActiveViewlet,
|
||||
viewOptionStore
|
||||
} from '@hcengineering/view-resources'
|
||||
import task from '../plugin'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import ViewletSettingButton from '@hcengineering/view-resources/src/components/ViewletSettingButton.svelte'
|
||||
import view, { Viewlet } from '@hcengineering/view'
|
||||
import { onDestroy } from 'svelte'
|
||||
import FilterBar from '@hcengineering/view-resources/src/components/filter/FilterBar.svelte'
|
||||
|
||||
export let _class: Ref<Class<Task>> = task.class.Task
|
||||
export let labelTasks = task.string.Tasks
|
||||
|
||||
let search = ''
|
||||
let resultQuery: DocumentQuery<Task> = {}
|
||||
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||
const assigned = { assignee: currentUser.employee }
|
||||
const created = { createdBy: currentUser._id }
|
||||
let subscribed = { _id: { $in: [] as Ref<Task>[] } }
|
||||
|
||||
$: baseQuery = updateBaseQuery(mode, { assigned, created, subscribed })
|
||||
function updateBaseQuery (mode: string, queries: { [key: string]: DocumentQuery<Task> }) {
|
||||
return { ...queries[mode] }
|
||||
}
|
||||
let searchQuery: DocumentQuery<Task> = { ...baseQuery }
|
||||
function updateSearchQuery (search: string): void {
|
||||
searchQuery = search === '' ? { ...baseQuery } : { ...baseQuery, $search: search }
|
||||
}
|
||||
$: if (baseQuery) updateSearchQuery(search)
|
||||
$: resultQuery = { ...searchQuery }
|
||||
|
||||
const client = getClient()
|
||||
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||
|
||||
let category: Ref<TagCategory> | undefined = undefined
|
||||
|
||||
let documentIds: Ref<Task>[] = []
|
||||
function updateResultQuery (search: string, documentIds: Ref<Task>[], doneStates: DoneState[]): void {
|
||||
resultQuery = search === '' ? {} : { $search: search }
|
||||
resultQuery.assignee = currentUser.employee
|
||||
resultQuery.doneState = { $nin: doneStates.map((it) => it._id) }
|
||||
if (documentIds.length > 0) {
|
||||
resultQuery._id = { $in: documentIds }
|
||||
@ -60,8 +84,57 @@
|
||||
).values()
|
||||
)
|
||||
})
|
||||
const subscribedQuery = createQuery()
|
||||
function getSubscribed () {
|
||||
subscribedQuery.query(
|
||||
_class,
|
||||
{ 'notification:mixin:Collaborators.collaborators': getCurrentAccount()._id },
|
||||
(result) => {
|
||||
const newSub = result.map((p) => p._id as Ref<Doc> as Ref<Task>)
|
||||
const curSub = subscribed._id.$in
|
||||
if (curSub.length !== newSub.length || curSub.some((id, i) => newSub[i] !== id)) {
|
||||
subscribed = { _id: { $in: newSub } }
|
||||
}
|
||||
},
|
||||
{ sort: { _id: 1 } }
|
||||
)
|
||||
}
|
||||
$: if (mode === 'subscribed') getSubscribed()
|
||||
const config: [string, IntlString, object][] = [
|
||||
['assigned', view.string.Assigned, {}],
|
||||
['created', view.string.Created, {}],
|
||||
['subscribed', view.string.Subscribed, {}]
|
||||
]
|
||||
let [[mode]] = config
|
||||
function handleChangeMode (newMode: string) {
|
||||
if (newMode === mode) return
|
||||
mode = newMode
|
||||
}
|
||||
$: modeSelectorProps = {
|
||||
config,
|
||||
mode,
|
||||
onChange: handleChangeMode
|
||||
} as IModeSelector
|
||||
|
||||
$: updateResultQuery(search, documentIds, doneStates)
|
||||
let viewlets: WithLookup<Viewlet>[] | undefined
|
||||
|
||||
let key = makeViewletKey()
|
||||
|
||||
onDestroy(
|
||||
resolvedLocationStore.subscribe((loc) => {
|
||||
key = makeViewletKey(loc)
|
||||
})
|
||||
)
|
||||
$: viewlet = viewlets && updateActiveViewlet(viewlets, active)
|
||||
$: active = $activeViewlet[key]
|
||||
const viewletQuery = createQuery()
|
||||
viewletQuery.query(view.class.Viewlet, { attachTo: _class }, (res) => (viewlets = res), {
|
||||
lookup: {
|
||||
descriptor: view.class.ViewletDescriptor
|
||||
}
|
||||
})
|
||||
$: viewOptions = getViewOptions(viewlet, $viewOptionStore)
|
||||
|
||||
function updateCategory (detail: { category: Ref<TagCategory> | null; elements: TagElement[] }) {
|
||||
category = detail.category ?? undefined
|
||||
@ -70,18 +143,34 @@
|
||||
const handleChange = (evt: any) => updateCategory(evt.detail)
|
||||
</script>
|
||||
|
||||
<div class="ac-header full divide caption-height">
|
||||
<div
|
||||
class="ac-header full divide"
|
||||
class:header-with-mode-selector={modeSelectorProps !== undefined}
|
||||
class:header-without-label={!labelTasks}
|
||||
>
|
||||
<div class="ac-header__wrap-title">
|
||||
<span class="ac-header__title"><Label label={labelTasks} /></span>
|
||||
{#if modeSelectorProps !== undefined}
|
||||
<ModeSelector props={modeSelectorProps} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<SearchEdit
|
||||
bind:value={search}
|
||||
on:change={() => {
|
||||
updateResultQuery(search, documentIds, doneStates)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="ac-header full divide search-start">
|
||||
<div class="ac-header-full small-gap">
|
||||
<SearchEdit
|
||||
bind:value={search}
|
||||
on:change={() => {
|
||||
updateResultQuery(search, documentIds, doneStates)
|
||||
}}
|
||||
/>
|
||||
<div class="buttons-divider" />
|
||||
<FilterButton {_class} />
|
||||
</div>
|
||||
{#if viewlet}
|
||||
<ViewletSettingButton bind:viewOptions {viewlet} />
|
||||
{/if}
|
||||
</div>
|
||||
<FilterBar {_class} query={searchQuery} {viewOptions} on:change={(e) => (resultQuery = e.detail)} />
|
||||
|
||||
<Component is={tags.component.TagsCategoryBar} props={{ targetClass: _class, category }} on:change={handleChange} />
|
||||
|
||||
|
@ -146,9 +146,7 @@
|
||||
"GotoMyIssues": "Go to my issues",
|
||||
"GotoTrackerApplication": "Switch to Tracker Application",
|
||||
|
||||
"Assigned": "Assigned",
|
||||
"Created": "Created",
|
||||
"Subscribed": "Subscribed",
|
||||
"CreatedOne": "Created",
|
||||
"MoveIssues": "Move issues",
|
||||
"MoveIssuesDescription": "Select the project you want to move issues to.",
|
||||
|
||||
|
@ -146,9 +146,7 @@
|
||||
"GotoMyIssues": "Перейи к моим задачам",
|
||||
"GotoTrackerApplication": "Перейти к приложению Трекер",
|
||||
|
||||
"Assigned": "Назначенные",
|
||||
"Created": "{value, plural, =1 {Создана} other {Созданные}}",
|
||||
"Subscribed": "Отслеживаемые",
|
||||
"CreatedOne": "Создана",
|
||||
"MoveIssues": "Переместить задачи",
|
||||
"MoveIssuesDescription": "Выберите проект, в который вы хотите переместить задачи.",
|
||||
|
||||
|
@ -396,7 +396,7 @@
|
||||
await subIssuesComponent.save(parents, _id)
|
||||
addNotification(await translate(tracker.string.IssueCreated, {}), getTitle(object.title), IssueNotification, {
|
||||
issueId: _id,
|
||||
subTitlePostfix: (await translate(tracker.string.Created, { value: 1 })).toLowerCase(),
|
||||
subTitlePostfix: (await translate(tracker.string.CreatedOne, {})).toLowerCase(),
|
||||
issueUrl: currentProject && generateIssueShortLink(getIssueId(currentProject, value as Issue))
|
||||
})
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
import IssuesView from './IssuesView.svelte'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { IModeSelector } from '../../utils'
|
||||
import { IModeSelector } from '@hcengineering/ui'
|
||||
|
||||
export let currentSpace: Ref<Project> | undefined = undefined
|
||||
export let baseQuery: DocumentQuery<Issue> = {}
|
||||
|
@ -1,12 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Ref, Space } from '@hcengineering/core'
|
||||
import { TabList, SearchEdit } from '@hcengineering/ui'
|
||||
import { TabList, SearchEdit, IModeSelector, ModeSelector } from '@hcengineering/ui'
|
||||
import { Viewlet } from '@hcengineering/view'
|
||||
import { FilterButton, setActiveViewletId } from '@hcengineering/view-resources'
|
||||
import tracker from '../../plugin'
|
||||
import { WithLookup } from '@hcengineering/core'
|
||||
import ModeSelector from '../ModeSelector.svelte'
|
||||
import { IModeSelector } from '../../utils'
|
||||
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
export let viewlet: WithLookup<Viewlet> | undefined
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { IntlString, translate } from '@hcengineering/platform'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Issue } from '@hcengineering/tracker'
|
||||
import { Button, IconDetails, IconDetailsFilled, resolvedLocationStore } from '@hcengineering/ui'
|
||||
import { Button, IconDetails, IconDetailsFilled, IModeSelector, resolvedLocationStore } from '@hcengineering/ui'
|
||||
import view, { Viewlet } from '@hcengineering/view'
|
||||
import {
|
||||
FilterBar,
|
||||
@ -18,7 +18,6 @@
|
||||
import tracker from '../../plugin'
|
||||
import IssuesContent from './IssuesContent.svelte'
|
||||
import IssuesHeader from './IssuesHeader.svelte'
|
||||
import { IModeSelector } from '../../utils'
|
||||
|
||||
export let space: Ref<Space> | undefined = undefined
|
||||
export let query: DocumentQuery<Issue> = {}
|
||||
|
@ -21,12 +21,13 @@
|
||||
|
||||
import tracker from '../../plugin'
|
||||
import IssuesView from '../issues/IssuesView.svelte'
|
||||
import { IModeSelector } from '../../utils'
|
||||
import { IModeSelector } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
const config: [string, IntlString, object][] = [
|
||||
['assigned', tracker.string.Assigned, {}],
|
||||
['created', tracker.string.Created, { value: 2 }],
|
||||
['subscribed', tracker.string.Subscribed, {}]
|
||||
['assigned', view.string.Assigned, {}],
|
||||
['created', view.string.Created, {}],
|
||||
['subscribed', view.string.Subscribed, {}]
|
||||
]
|
||||
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||
const assigned = { assignee: currentUser.employee }
|
||||
|
@ -193,9 +193,7 @@ export default mergeIds(trackerId, tracker, {
|
||||
AllFilters: '' as IntlString,
|
||||
NoDescription: '' as IntlString,
|
||||
|
||||
Assigned: '' as IntlString,
|
||||
Created: '' as IntlString,
|
||||
Subscribed: '' as IntlString,
|
||||
CreatedOne: '' as IntlString,
|
||||
|
||||
Relations: '' as IntlString,
|
||||
RemoveRelation: '' as IntlString,
|
||||
|
@ -650,12 +650,3 @@ export function issueToAttachedData (issue: Issue): AttachedData<Issue> {
|
||||
const { _id, _class, space, ...data } = issue
|
||||
return { ...data }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface IModeSelector {
|
||||
mode: string
|
||||
config: Array<[string, IntlString, object]>
|
||||
onChange: (_mode: string) => void
|
||||
}
|
||||
|
@ -28,6 +28,10 @@
|
||||
"SelectItemAll": "Select all items",
|
||||
"SelectItemNone": "Deselect all items",
|
||||
|
||||
"Assigned": "Assigned",
|
||||
"Created": "Created",
|
||||
"Subscribed": "Subscribed",
|
||||
|
||||
"ShowPreview": "Show document preview",
|
||||
"ShowActions": "Show actions popup",
|
||||
|
||||
|
@ -31,6 +31,9 @@
|
||||
"SelectItem": "Выбрать",
|
||||
"SelectItemAll": "Выбрать все",
|
||||
"SelectItemNone": "Снять все выделения",
|
||||
"Assigned": "Назначенные",
|
||||
"Created": "Созданные",
|
||||
"Subscribed": "Отслеживаемые",
|
||||
"ShowPreview": "Предпросмотр документа",
|
||||
"ShowActions": "Показать действия",
|
||||
"RestoreDefaults": "По умолчанию",
|
||||
|
@ -725,7 +725,10 @@ const view = plugin(viewId, {
|
||||
AddSavedView: '' as IntlString,
|
||||
Timeline: '' as IntlString,
|
||||
Public: '' as IntlString,
|
||||
Hide: '' as IntlString
|
||||
Hide: '' as IntlString,
|
||||
Assigned: '' as IntlString,
|
||||
Created: '' as IntlString,
|
||||
Subscribed: '' as IntlString
|
||||
},
|
||||
icon: {
|
||||
Table: '' as Asset,
|
||||
|
@ -14,7 +14,7 @@ test.describe('workbench tests', () => {
|
||||
await page.click('[id="app-recruit\\:string\\:RecruitApplication"]')
|
||||
await expect(page).toHaveURL(`${PlatformURI}/workbench/sanity-ws/recruit`)
|
||||
// Click text=Applications
|
||||
await page.click('text=Applications')
|
||||
await page.click('text=/^Applications/')
|
||||
await expect(page).toHaveURL(`${PlatformURI}/workbench/sanity-ws/recruit/candidates`)
|
||||
// Click text=Applications Application >> span
|
||||
await expect(page.locator('text=Applications >> nth=1')).toBeVisible()
|
||||
|
Loading…
Reference in New Issue
Block a user