mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-14 04:08:19 +00:00
Upcoming events (#1195)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
90955638c5
commit
6f7e95e233
@ -41,7 +41,8 @@ import workbench from '@anticrm/model-workbench'
|
|||||||
import { Applicant, Candidate, Candidates, Vacancy } from '@anticrm/recruit'
|
import { Applicant, Candidate, Candidates, Vacancy } from '@anticrm/recruit'
|
||||||
import { TOpinion, TReview, TReviewCategory } from './review-model'
|
import { TOpinion, TReview, TReviewCategory } from './review-model'
|
||||||
import recruit from './plugin'
|
import recruit from './plugin'
|
||||||
import { createReviewModel } from './review'
|
import { createReviewModel, reviewTableConfig, reviewTableOptions } from './review'
|
||||||
|
import calendar from '@anticrm/model-calendar'
|
||||||
|
|
||||||
@Model(recruit.class.Vacancy, task.class.SpaceWithStates)
|
@Model(recruit.class.Vacancy, task.class.SpaceWithStates)
|
||||||
@UX(recruit.string.Vacancy, recruit.icon.Vacancy)
|
@UX(recruit.string.Vacancy, recruit.icon.Vacancy)
|
||||||
@ -192,7 +193,18 @@ export function createModel (builder: Builder): void {
|
|||||||
componentProps: {
|
componentProps: {
|
||||||
labelTasks: recruit.string.Applications,
|
labelTasks: recruit.string.Applications,
|
||||||
_class: recruit.class.Applicant
|
_class: recruit.class.Applicant
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'upcoming',
|
||||||
|
component: calendar.component.UpcomingEvents,
|
||||||
|
componentProps: {
|
||||||
|
_class: recruit.class.Review,
|
||||||
|
options: reviewTableOptions,
|
||||||
|
config: reviewTableConfig
|
||||||
},
|
},
|
||||||
|
icon: calendar.icon.Calendar,
|
||||||
|
label: calendar.string.UpcomingEvents,
|
||||||
position: 'top'
|
position: 'top'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,12 +1,38 @@
|
|||||||
import { Doc, FindOptions } from '@anticrm/core'
|
import { FindOptions } from '@anticrm/core'
|
||||||
import { Builder } from '@anticrm/model'
|
import { Builder } from '@anticrm/model'
|
||||||
|
import calendar from '@anticrm/model-calendar'
|
||||||
import contact from '@anticrm/model-contact'
|
import contact from '@anticrm/model-contact'
|
||||||
import core from '@anticrm/model-core'
|
import core from '@anticrm/model-core'
|
||||||
import task from '@anticrm/model-task'
|
import task from '@anticrm/model-task'
|
||||||
import view from '@anticrm/model-view'
|
import view from '@anticrm/model-view'
|
||||||
import workbench from '@anticrm/model-workbench'
|
import workbench from '@anticrm/model-workbench'
|
||||||
|
import { Review } from '@anticrm/recruit'
|
||||||
|
import { BuildModelKey } from '@anticrm/view'
|
||||||
import recruit from './plugin'
|
import recruit from './plugin'
|
||||||
import calendar from '@anticrm/model-calendar'
|
|
||||||
|
export const reviewTableOptions: FindOptions<Review> = {
|
||||||
|
lookup: {
|
||||||
|
attachedTo: recruit.mixin.Candidate,
|
||||||
|
participants: contact.class.Employee,
|
||||||
|
company: contact.class.Organization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const reviewTableConfig: (BuildModelKey | string)[] = [
|
||||||
|
'',
|
||||||
|
'title',
|
||||||
|
'$lookup.attachedTo',
|
||||||
|
// 'verdict',
|
||||||
|
{ key: '', presenter: recruit.component.OpinionsPresenter, label: recruit.string.Opinions, sortingKey: 'opinions' },
|
||||||
|
{
|
||||||
|
key: '$lookup.participants',
|
||||||
|
presenter: calendar.component.PersonsPresenter,
|
||||||
|
label: calendar.string.Participants,
|
||||||
|
sortingKey: '$lookup.participants'
|
||||||
|
},
|
||||||
|
'$lookup.company',
|
||||||
|
{ key: '', presenter: calendar.component.DateTimePresenter, label: calendar.string.Date, sortingKey: 'date' },
|
||||||
|
'modifiedOn'
|
||||||
|
]
|
||||||
|
|
||||||
export function createReviewModel (builder: Builder): void {
|
export function createReviewModel (builder: Builder): void {
|
||||||
builder.mixin(recruit.class.ReviewCategory, core.class.Class, workbench.mixin.SpaceView, {
|
builder.mixin(recruit.class.ReviewCategory, core.class.Class, workbench.mixin.SpaceView, {
|
||||||
@ -82,27 +108,20 @@ export function createReviewModel (builder: Builder): void {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const reviewOptions: FindOptions<Review> = {
|
||||||
|
lookup: {
|
||||||
|
attachedTo: recruit.mixin.Candidate,
|
||||||
|
participants: contact.class.Employee,
|
||||||
|
company: contact.class.Organization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||||
attachTo: recruit.class.Review,
|
attachTo: recruit.class.Review,
|
||||||
descriptor: calendar.viewlet.Calendar,
|
descriptor: calendar.viewlet.Calendar,
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
options: {
|
options: reviewOptions,
|
||||||
lookup: {
|
config: reviewTableConfig
|
||||||
attachedTo: recruit.mixin.Candidate,
|
|
||||||
participants: contact.class.Employee,
|
|
||||||
company: contact.class.Organization
|
|
||||||
}
|
|
||||||
} as FindOptions<Doc>,
|
|
||||||
config: [
|
|
||||||
'',
|
|
||||||
'title',
|
|
||||||
'$lookup.attachedTo',
|
|
||||||
'verdict',
|
|
||||||
{ key: '', presenter: recruit.component.OpinionsPresenter, label: recruit.string.Opinions, sortingKey: 'opinions' },
|
|
||||||
{ key: '$lookup.participants', presenter: calendar.component.PersonsPresenter, label: calendar.string.Participants, sortingKey: '$lookup.participants' },
|
|
||||||
'$lookup.company',
|
|
||||||
'modifiedOn'
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,25 +130,8 @@ function createTableViewlet (builder: Builder): void {
|
|||||||
attachTo: recruit.class.Review,
|
attachTo: recruit.class.Review,
|
||||||
descriptor: view.viewlet.Table,
|
descriptor: view.viewlet.Table,
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
options: {
|
options: reviewTableOptions,
|
||||||
lookup: {
|
config: reviewTableConfig
|
||||||
attachedTo: recruit.mixin.Candidate,
|
|
||||||
participants: contact.class.Employee,
|
|
||||||
company: contact.class.Organization
|
|
||||||
}
|
|
||||||
} as FindOptions<Doc>,
|
|
||||||
config: [
|
|
||||||
'',
|
|
||||||
'title',
|
|
||||||
'$lookup.attachedTo',
|
|
||||||
'verdict',
|
|
||||||
{ key: '', presenter: recruit.component.OpinionsPresenter, label: recruit.string.Opinions, sortingKey: 'opinions' },
|
|
||||||
{ key: '$lookup.participants', presenter: calendar.component.PersonsPresenter, label: calendar.string.Participants, sortingKey: '$lookup.participants' },
|
|
||||||
'$lookup.company',
|
|
||||||
'date',
|
|
||||||
'dueDate',
|
|
||||||
'modifiedOn'
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
builder.mixin(recruit.class.Opinion, core.class.Class, view.mixin.AttributeEditor, {
|
builder.mixin(recruit.class.Opinion, core.class.Class, view.mixin.AttributeEditor, {
|
||||||
|
@ -32,7 +32,7 @@ export type QuerySelector<T> = {
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type ObjQueryType<T> = T | QuerySelector<T>
|
export type ObjQueryType<T> = (T extends Array<infer U> ? U | U[] : T) | QuerySelector<T>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -50,23 +50,26 @@ export type DocumentQuery<T extends Doc> = {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type ToClassRefT<T extends object, P extends keyof T> = T[P] extends Ref<infer X> | null | undefined ? Ref<Class<X>> | [Ref<Class<X>>, Lookup<X>] : never
|
export type ToClassRefT<T extends object, P extends keyof T> = T[P] extends Ref<infer X> | null | undefined ? Ref<Class<X>> | [Ref<Class<X>>, Lookup<X>] : never
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type ToClassRefTA<T extends object, P extends keyof T> = T[P] extends Array<Ref<infer X>> | null | undefined ? Ref<Class<X>> | [Ref<Class<X>>, Lookup<X>] : never
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type ToClassRef<T extends object> = {
|
export type ToClassRef<T extends object> = {
|
||||||
[P in keyof T]?: ToClassRefT<T, P>
|
[P in keyof T]?: ToClassRefT<T, P> | ToClassRefTA<T, P>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type RefKeys<T extends Doc> = Pick<T, KeysByType<T, NullableRef>>
|
export type NullableRef = Ref<Doc> | Array<Ref<Doc>> | null | undefined
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type NullableRef = Ref<Doc> | null | undefined
|
export type RefKeys<T extends Doc> = Pick<T, KeysByType<T, NullableRef>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
|
@ -116,7 +116,7 @@ export interface MoveDescriptor<X extends PropertyType> {
|
|||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type ArrayAsElementPosition<T extends object> = {
|
export type ArrayAsElementPosition<T extends object> = {
|
||||||
[P in keyof T]: T[P] extends Arr<infer X> ? X | Position<X> : never
|
[P in keyof T]-?: T[P] extends Arr<infer X> ? X | Position<X> : never
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,10 +58,18 @@ export const test = plugin('test' as Plugin, {
|
|||||||
TestMixin: '' as Ref<Mixin<TestMixin>>
|
TestMixin: '' as Ref<Mixin<TestMixin>>
|
||||||
},
|
},
|
||||||
class: {
|
class: {
|
||||||
TestComment: '' as Ref<Class<AttachedComment>>
|
TestComment: '' as Ref<Class<AttachedComment>>,
|
||||||
|
ParticipantsHolder: '' as Ref<Class<ParticipantsHolder>>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export interface ParticipantsHolder extends Doc {
|
||||||
|
participants?: Ref<Doc>[]
|
||||||
|
}
|
||||||
|
|
||||||
const DOMAIN_TEST: Domain = 'test' as Domain
|
const DOMAIN_TEST: Domain = 'test' as Domain
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,6 +98,8 @@ export function genMinModel (): TxCUD<Doc>[] {
|
|||||||
|
|
||||||
txes.push(createClass(test.class.TestComment, { label: 'TestComment' as IntlString, extends: core.class.AttachedDoc, kind: ClassifierKind.CLASS, domain: DOMAIN_TEST }))
|
txes.push(createClass(test.class.TestComment, { label: 'TestComment' as IntlString, extends: core.class.AttachedDoc, kind: ClassifierKind.CLASS, domain: DOMAIN_TEST }))
|
||||||
|
|
||||||
|
txes.push(createClass(test.class.ParticipantsHolder, { label: 'ParticipantsHolder' as IntlString, extends: core.class.Doc, kind: ClassifierKind.CLASS, domain: DOMAIN_TEST }))
|
||||||
|
|
||||||
const u1 = 'User1' as Ref<Account>
|
const u1 = 'User1' as Ref<Account>
|
||||||
const u2 = 'User2' as Ref<Account>
|
const u2 = 'User2' as Ref<Account>
|
||||||
txes.push(
|
txes.push(
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import core, { createClient, Doc, generateId, Ref, SortingOrder, Space, Tx, TxCreateDoc, TxOperations, WithLookup } from '@anticrm/core'
|
import core, { createClient, Doc, generateId, Ref, SortingOrder, Space, Tx, TxCreateDoc, TxOperations, WithLookup } from '@anticrm/core'
|
||||||
import { AttachedComment, test, genMinModel } from './minmodel'
|
|
||||||
import { LiveQuery } from '..'
|
import { LiveQuery } from '..'
|
||||||
import { connect } from './connection'
|
import { connect } from './connection'
|
||||||
|
import { AttachedComment, genMinModel, ParticipantsHolder, test } from './minmodel'
|
||||||
|
|
||||||
interface Channel extends Space {
|
interface Channel extends Space {
|
||||||
x: number
|
x: number
|
||||||
@ -49,12 +49,12 @@ describe('query', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((resolve) => {
|
const result = await new Promise((resolve) => {
|
||||||
liveQuery.query<Space>(core.class.Space, { private: false }, (result) => {
|
liveQuery.query<Space>(core.class.Space, { private: false }, (result) => {
|
||||||
expect(result).toHaveLength(expectedLength)
|
resolve(result)
|
||||||
resolve(null)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
expect(result).toHaveLength(expectedLength)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('query should be live', async () => {
|
it('query should be live', async () => {
|
||||||
@ -743,4 +743,51 @@ describe('query', () => {
|
|||||||
// }
|
// }
|
||||||
// await pp
|
// await pp
|
||||||
// })
|
// })
|
||||||
|
|
||||||
|
it('update-array-value', async () => {
|
||||||
|
const { liveQuery, factory } = await getClient()
|
||||||
|
|
||||||
|
const spaces = await liveQuery.findAll(core.class.Space, {})
|
||||||
|
await factory.createDoc(test.class.ParticipantsHolder, spaces[0]._id, {
|
||||||
|
participants: ['a' as Ref<Doc>]
|
||||||
|
})
|
||||||
|
const a2 = await factory.createDoc(test.class.ParticipantsHolder, spaces[0]._id, {
|
||||||
|
participants: ['b' as Ref<Doc>]
|
||||||
|
})
|
||||||
|
|
||||||
|
const holderBefore = await liveQuery.findAll(test.class.ParticipantsHolder, { participants: 'a' as Ref<Doc> })
|
||||||
|
expect(holderBefore.length).toEqual(1)
|
||||||
|
|
||||||
|
let attempt = 0
|
||||||
|
let resolvePpv: (value: Doc[] | PromiseLike<Doc[]>) => void
|
||||||
|
|
||||||
|
const resolveP = new Promise<Doc[]>((resolve) => {
|
||||||
|
resolvePpv = resolve
|
||||||
|
})
|
||||||
|
const pp = await new Promise((resolve) => {
|
||||||
|
liveQuery.query<Space>(
|
||||||
|
test.class.ParticipantsHolder,
|
||||||
|
{ participants: 'a' as Ref<Doc> },
|
||||||
|
(result) => {
|
||||||
|
if (attempt > 0) {
|
||||||
|
resolvePpv(result)
|
||||||
|
} else {
|
||||||
|
resolve(null)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ sort: { private: SortingOrder.Ascending } }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await pp // We have first value returned
|
||||||
|
|
||||||
|
attempt++
|
||||||
|
await factory.updateDoc<ParticipantsHolder>(test.class.ParticipantsHolder, spaces[0]._id, a2, {
|
||||||
|
$push: {
|
||||||
|
participants: 'a' as Ref<Doc>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const result = await resolveP
|
||||||
|
expect(result.length).toEqual(2)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -338,9 +338,19 @@ export class LiveQuery extends TxProcessor implements Client {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const doc: Doc = {
|
||||||
|
_id: tx.objectId,
|
||||||
|
_class: tx.objectClass,
|
||||||
|
modifiedBy: tx.modifiedBy,
|
||||||
|
modifiedOn: tx.modifiedOn,
|
||||||
|
space: tx.objectSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
TxProcessor.updateDoc2Doc(doc, tx)
|
||||||
|
|
||||||
for (const key in q.query) {
|
for (const key in q.query) {
|
||||||
const value = (q.query as any)[key]
|
const value = (q.query as any)[key]
|
||||||
const res = findProperty([tx.operations as unknown as Doc], key, value)
|
const res = findProperty([doc], key, value)
|
||||||
if (res.length === 1) {
|
if (res.length === 1) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,11 @@
|
|||||||
"ModeWeek": "Week",
|
"ModeWeek": "Week",
|
||||||
"ModeMonth": "Month",
|
"ModeMonth": "Month",
|
||||||
"ModeYear": "Year",
|
"ModeYear": "Year",
|
||||||
"Today": "Today"
|
"Today": "Today",
|
||||||
|
"UpcomingEvents": "Upcoming events",
|
||||||
|
"TableView": "Table",
|
||||||
|
"DueMinutes": "{minutes, plural, =0 {less than a minute} =1 {a minute} other {# minutes}}",
|
||||||
|
"DueHours": "{hours, plural, =0 {less than an hour} =1 {1 hour} other {# hours}}",
|
||||||
|
"DueDays": "{days, plural, =0 {today} =1 {1 day} other {# days}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -20,6 +20,11 @@
|
|||||||
"ModeWeek": "Неделя",
|
"ModeWeek": "Неделя",
|
||||||
"ModeMonth": "Месяц",
|
"ModeMonth": "Месяц",
|
||||||
"ModeYear": "Год",
|
"ModeYear": "Год",
|
||||||
"Today": "Сегодня"
|
"Today": "Сегодня",
|
||||||
|
"UpcomingEvents": "Предстоящие события",
|
||||||
|
"TableView": "Таблица",
|
||||||
|
"DueMinutes": "{minutes, plural, =0 {меньше минуты} =1 {минута} other {# минут}}",
|
||||||
|
"DueHours": "{hours, plural, =0 {меньше часа} =1 {1 час} other {# часы}}",
|
||||||
|
"DueDays": "{days, plural, =0 {сегодня} =1 {1 день} other {# дня}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -38,7 +38,8 @@
|
|||||||
|
|
||||||
let loading = false
|
let loading = false
|
||||||
let resultQuery: DocumentQuery<Event>
|
let resultQuery: DocumentQuery<Event>
|
||||||
$: resultQuery = search === '' ? { ...query, space } : { ...query, $search: search, space }
|
$: spaceOpt = (space ? { space } : {})
|
||||||
|
$: resultQuery = search === '' ? { ...query, ...spaceOpt } : { ...query, $search: search, ...spaceOpt }
|
||||||
|
|
||||||
let objects: Event[] = []
|
let objects: Event[] = []
|
||||||
|
|
||||||
@ -73,14 +74,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findEvents (events: Event[], date: Date): Event[] {
|
function findEvents (events: Event[], date: Date): Event[] {
|
||||||
return events.filter((it) => areDatesLess(new Date(it.date), date) && areDatesLess(date, new Date(it.dueDate ?? it.date)))
|
return events.filter(
|
||||||
|
(it) => areDatesLess(new Date(it.date), date) && areDatesLess(date, new Date(it.dueDate ?? it.date))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ShiftType {
|
interface ShiftType {
|
||||||
yearShift: number
|
yearShift: number
|
||||||
monthShift: number
|
monthShift: number
|
||||||
dayShift: number
|
dayShift: number
|
||||||
weekShift:number
|
weekShift: number
|
||||||
}
|
}
|
||||||
|
|
||||||
let shifts: ShiftType = {
|
let shifts: ShiftType = {
|
||||||
@ -134,7 +137,12 @@
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CalendarMode { Day, Week, Month, Year }
|
enum CalendarMode {
|
||||||
|
Day,
|
||||||
|
Week,
|
||||||
|
Month,
|
||||||
|
Year
|
||||||
|
}
|
||||||
|
|
||||||
let mode: CalendarMode = CalendarMode.Year
|
let mode: CalendarMode = CalendarMode.Year
|
||||||
|
|
||||||
@ -156,7 +164,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class='fs-title ml-2 mb-2 flex-row-center'>
|
<div class="fs-title ml-2 mb-2 flex-row-center">
|
||||||
{label(currentDate(date, shifts), mode)}
|
{label(currentDate(date, shifts), mode)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -202,29 +210,63 @@
|
|||||||
}
|
}
|
||||||
mode = CalendarMode.Year
|
mode = CalendarMode.Year
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div class="flex ml-4 gap-1">
|
<div class="flex ml-4 gap-1">
|
||||||
<Button icon={IconBack} size={'small'} on:click={() => { inc(-1) } }/>
|
<Button
|
||||||
<Button size={'small'} label={calendar.string.Today} on:click={() => { inc(0) }}/>
|
icon={IconBack}
|
||||||
<Button icon={IconForward} size={'small'} on:click={() => { inc(1) }}/>
|
size={'small'}
|
||||||
|
on:click={() => {
|
||||||
|
inc(-1)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size={'small'}
|
||||||
|
label={calendar.string.Today}
|
||||||
|
on:click={() => {
|
||||||
|
inc(0)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon={IconForward}
|
||||||
|
size={'small'}
|
||||||
|
on:click={() => {
|
||||||
|
inc(1)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{#if mode === CalendarMode.Year}
|
{#if mode === CalendarMode.Year}
|
||||||
<ScrollBox bothScroll>
|
<ScrollBox bothScroll>
|
||||||
<YearCalendar {mondayStart} cellHeight={'2.5rem'} bind:value={value} currentDate={currentDate(date, shifts)}>
|
<YearCalendar {mondayStart} cellHeight={'2.5rem'} bind:value currentDate={currentDate(date, shifts)}>
|
||||||
<svelte:fragment slot="cell" let:date>
|
<svelte:fragment slot="cell" let:date>
|
||||||
<Day events={findEvents(objects, date)} {date} {_class} {baseMenuClass} {options} {config} query={resultQuery} />
|
<Day
|
||||||
|
events={findEvents(objects, date)}
|
||||||
|
{date}
|
||||||
|
{_class}
|
||||||
|
{baseMenuClass}
|
||||||
|
{options}
|
||||||
|
{config}
|
||||||
|
query={resultQuery}
|
||||||
|
/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</YearCalendar>
|
</YearCalendar>
|
||||||
</ScrollBox>
|
</ScrollBox>
|
||||||
{:else if mode === CalendarMode.Month}
|
{:else if mode === CalendarMode.Month}
|
||||||
<div class='flex flex-grow'>
|
<div class="flex flex-grow">
|
||||||
<MonthCalendar {mondayStart} cellHeight={'8.5rem'} bind:value={value} currentDate={currentDate(date, shifts)}>
|
<MonthCalendar {mondayStart} cellHeight={'8.5rem'} bind:value currentDate={currentDate(date, shifts)}>
|
||||||
<svelte:fragment slot="cell" let:date={date}>
|
<svelte:fragment slot="cell" let:date>
|
||||||
<Day events={findEvents(objects, date)} {date} size={'huge'} {_class} {baseMenuClass} {options} {config} query={resultQuery}/>
|
<Day
|
||||||
</svelte:fragment>
|
events={findEvents(objects, date)}
|
||||||
</MonthCalendar>
|
{date}
|
||||||
</div>
|
size={'huge'}
|
||||||
{/if}
|
{_class}
|
||||||
|
{baseMenuClass}
|
||||||
|
{options}
|
||||||
|
{config}
|
||||||
|
query={resultQuery}
|
||||||
|
/>
|
||||||
|
</svelte:fragment>
|
||||||
|
</MonthCalendar>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2022 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 { Event } from '@anticrm/calendar'
|
||||||
|
import { translate } from '@anticrm/platform'
|
||||||
|
import { DatePresenter } from '@anticrm/ui'
|
||||||
|
import calendar from '../plugin'
|
||||||
|
|
||||||
|
export let value: Event
|
||||||
|
|
||||||
|
$: date = value ? new Date(value.date) : undefined
|
||||||
|
$: dueDate = value ? new Date(value.dueDate ?? value.date) : undefined
|
||||||
|
|
||||||
|
$: interval = (value.dueDate ?? value.date) - value.date
|
||||||
|
|
||||||
|
const SECOND = 1000
|
||||||
|
const MINUTE = SECOND * 60
|
||||||
|
const HOUR = MINUTE * 60
|
||||||
|
const DAY = HOUR * 24
|
||||||
|
|
||||||
|
async function formatDueDate (interval: number): Promise<string> {
|
||||||
|
let passed = interval
|
||||||
|
if (interval < 0) passed = 0
|
||||||
|
if (passed < HOUR) {
|
||||||
|
return await translate(calendar.string.DueMinutes, { minutes: Math.floor(passed / MINUTE) })
|
||||||
|
} else if (passed < DAY) {
|
||||||
|
return await translate(calendar.string.DueHours, { hours: Math.floor(passed / HOUR) })
|
||||||
|
} else {
|
||||||
|
return await translate(calendar.string.DueDays, { days: Math.floor(passed / DAY) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="antiSelect">
|
||||||
|
{#if date}
|
||||||
|
<DatePresenter value={date} withTime={date.getMinutes() !== 0 && date.getHours() !== 0 && interval < DAY} />
|
||||||
|
{#if interval > 0}
|
||||||
|
{#await formatDueDate(interval) then t}
|
||||||
|
<span class='ml-2 mr-1 whitespace-nowrap'>({t})</span>
|
||||||
|
{/await}
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
No date
|
||||||
|
{/if}
|
||||||
|
</div>
|
116
plugins/calendar-resources/src/components/UpcomingEvents.svelte
Normal file
116
plugins/calendar-resources/src/components/UpcomingEvents.svelte
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2022 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 { Event } from '@anticrm/calendar'
|
||||||
|
import { EmployeeAccount } from '@anticrm/contact'
|
||||||
|
import { Class, DocumentQuery, FindOptions, getCurrentAccount, Ref } from '@anticrm/core'
|
||||||
|
import { Asset, IntlString } from '@anticrm/platform'
|
||||||
|
import { AnySvelteComponent, Icon, Label, SearchEdit, Tooltip } from '@anticrm/ui'
|
||||||
|
import { Table } from '@anticrm/view-resources'
|
||||||
|
import view from '@anticrm/view'
|
||||||
|
import calendar from '../plugin'
|
||||||
|
import CalendarView from './CalendarView.svelte'
|
||||||
|
|
||||||
|
export let _class: Ref<Class<Event>>
|
||||||
|
export let query: DocumentQuery<Event> = {}
|
||||||
|
export let options: FindOptions<Event> | undefined = undefined
|
||||||
|
export let baseMenuClass: Ref<Class<Event>> | undefined = undefined
|
||||||
|
export let config: string[]
|
||||||
|
|
||||||
|
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||||
|
|
||||||
|
let search = ''
|
||||||
|
let resultQuery: DocumentQuery<Event> = {}
|
||||||
|
|
||||||
|
function updateResultQuery (search: string): void {
|
||||||
|
resultQuery = search === '' ? { ...query } : { ...query, $search: search }
|
||||||
|
|
||||||
|
resultQuery.participants = currentUser.employee
|
||||||
|
}
|
||||||
|
|
||||||
|
$: updateResultQuery(search)
|
||||||
|
|
||||||
|
interface CalendarViewlet {
|
||||||
|
component: AnySvelteComponent
|
||||||
|
props: Record<string, any>
|
||||||
|
label: IntlString
|
||||||
|
icon: Asset
|
||||||
|
}
|
||||||
|
|
||||||
|
$: viewlets = [{
|
||||||
|
component: CalendarView,
|
||||||
|
icon: calendar.icon.Calendar,
|
||||||
|
label: calendar.string.Calendar,
|
||||||
|
props: {
|
||||||
|
_class,
|
||||||
|
space: undefined,
|
||||||
|
query: resultQuery,
|
||||||
|
options,
|
||||||
|
baseMenuClass,
|
||||||
|
config,
|
||||||
|
search
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: Table,
|
||||||
|
icon: view.icon.Table,
|
||||||
|
label: calendar.string.TableView,
|
||||||
|
props: {
|
||||||
|
_class,
|
||||||
|
query: resultQuery,
|
||||||
|
options,
|
||||||
|
baseMenuClass,
|
||||||
|
config,
|
||||||
|
search
|
||||||
|
}
|
||||||
|
}] as CalendarViewlet[]
|
||||||
|
let selectedViewlet = 0
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="ac-header full">
|
||||||
|
<div class="ac-header__wrap-title">
|
||||||
|
<div class="ac-header__icon"><Icon icon={calendar.icon.Calendar} size={'small'} /></div>
|
||||||
|
<span class="ac-header__title"><Label label={calendar.string.UpcomingEvents} /></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if viewlets.length > 1}
|
||||||
|
<div class="flex">
|
||||||
|
{#each viewlets as viewlet, i}
|
||||||
|
<Tooltip label={viewlet.label} direction={'top'}>
|
||||||
|
<button
|
||||||
|
class="ac-header__icon-button"
|
||||||
|
class:selected={selectedViewlet === i}
|
||||||
|
on:click={() => {
|
||||||
|
selectedViewlet = i
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon icon={viewlet.icon} size={'small'} />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<SearchEdit
|
||||||
|
bind:value={search}
|
||||||
|
on:change={() => {
|
||||||
|
updateResultQuery(search)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if viewlets[selectedViewlet]}
|
||||||
|
<svelte:component this={viewlets[selectedViewlet].component} {...(viewlets[selectedViewlet].props)} />
|
||||||
|
{/if}
|
@ -17,10 +17,14 @@ import { Resources } from '@anticrm/platform'
|
|||||||
|
|
||||||
import PersonsPresenter from './components/PersonsPresenter.svelte'
|
import PersonsPresenter from './components/PersonsPresenter.svelte'
|
||||||
import CalendarView from './components/CalendarView.svelte'
|
import CalendarView from './components/CalendarView.svelte'
|
||||||
|
import UpcomingEvents from './components/UpcomingEvents.svelte'
|
||||||
|
import DateTimePresenter from './components/DateTimePresenter.svelte'
|
||||||
|
|
||||||
export default async (): Promise<Resources> => ({
|
export default async (): Promise<Resources> => ({
|
||||||
component: {
|
component: {
|
||||||
PersonsPresenter,
|
PersonsPresenter,
|
||||||
CalendarView
|
CalendarView,
|
||||||
|
UpcomingEvents,
|
||||||
|
DateTimePresenter
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -25,6 +25,11 @@ export default mergeIds(calendarId, calendar, {
|
|||||||
ModeWeek: '' as IntlString,
|
ModeWeek: '' as IntlString,
|
||||||
ModeMonth: '' as IntlString,
|
ModeMonth: '' as IntlString,
|
||||||
ModeYear: '' as IntlString,
|
ModeYear: '' as IntlString,
|
||||||
Today: '' as IntlString
|
Today: '' as IntlString,
|
||||||
|
UpcomingEvents: '' as IntlString,
|
||||||
|
TableView: '' as IntlString,
|
||||||
|
DueMinutes: '' as IntlString,
|
||||||
|
DueHours: '' as IntlString,
|
||||||
|
DueDays: '' as IntlString
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -69,7 +69,9 @@ const calendarPlugin = plugin(calendarId, {
|
|||||||
Calendar: '' as Ref<Doc>
|
Calendar: '' as Ref<Doc>
|
||||||
},
|
},
|
||||||
component: {
|
component: {
|
||||||
PersonsPresenter: '' as AnyComponent
|
PersonsPresenter: '' as AnyComponent,
|
||||||
|
UpcomingEvents: '' as AnyComponent,
|
||||||
|
DateTimePresenter: '' as AnyComponent
|
||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
Title: '' as IntlString,
|
Title: '' as IntlString,
|
||||||
|
@ -13,10 +13,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Contact, Organization, Person } from '@anticrm/contact'
|
import type { Contact, EmployeeAccount, Organization, Person } from '@anticrm/contact'
|
||||||
import contact from '@anticrm/contact'
|
import contact from '@anticrm/contact'
|
||||||
import { OrganizationSelector } from '@anticrm/contact-resources'
|
import { OrganizationSelector } from '@anticrm/contact-resources'
|
||||||
import { Account, Class, Client, Doc, generateId, Ref } from '@anticrm/core'
|
import { Account, Class, Client, Doc, generateId, getCurrentAccount, Ref } from '@anticrm/core'
|
||||||
import { getResource, OK, Resource, Severity, Status } from '@anticrm/platform'
|
import { getResource, OK, Resource, Severity, Status } from '@anticrm/platform'
|
||||||
import { Card, getClient, UserBox } from '@anticrm/presentation'
|
import { Card, getClient, UserBox } from '@anticrm/presentation'
|
||||||
import type { Candidate, Review } from '@anticrm/recruit'
|
import type { Candidate, Review } from '@anticrm/recruit'
|
||||||
@ -32,6 +32,8 @@
|
|||||||
|
|
||||||
export let preserveCandidate = false
|
export let preserveCandidate = false
|
||||||
|
|
||||||
|
const currentUser = getCurrentAccount() as EmployeeAccount
|
||||||
|
|
||||||
let status: Status = OK
|
let status: Status = OK
|
||||||
|
|
||||||
let title: string = ''
|
let title: string = ''
|
||||||
@ -56,7 +58,8 @@
|
|||||||
description,
|
description,
|
||||||
company,
|
company,
|
||||||
verdict: '',
|
verdict: '',
|
||||||
title
|
title,
|
||||||
|
participants: [currentUser.employee]
|
||||||
}
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
import type { Doc, Ref } from '@anticrm/core'
|
import type { Doc, Ref } from '@anticrm/core'
|
||||||
import core from '@anticrm/core'
|
import core from '@anticrm/core'
|
||||||
import { IntlString } from '@anticrm/platform'
|
import { IntlString } from '@anticrm/platform'
|
||||||
import task from '@anticrm/task'
|
import calendar from '@anticrm/calendar'
|
||||||
import { CircleButton, IconAdd, Label, showPopup } from '@anticrm/ui'
|
import { CircleButton, IconAdd, Label, showPopup } from '@anticrm/ui'
|
||||||
import { Table } from '@anticrm/view-resources'
|
import { Table } from '@anticrm/view-resources'
|
||||||
import recruit from '../../plugin'
|
import recruit from '../../plugin'
|
||||||
@ -50,8 +50,7 @@
|
|||||||
label: recruit.string.Opinions,
|
label: recruit.string.Opinions,
|
||||||
sortingKey: 'opinions'
|
sortingKey: 'opinions'
|
||||||
},
|
},
|
||||||
'date',
|
{ key: '', presenter: calendar.component.DateTimePresenter, label: calendar.string.Date, sortingKey: 'date' },
|
||||||
'dueDate'
|
|
||||||
]}
|
]}
|
||||||
options={{
|
options={{
|
||||||
lookup: {
|
lookup: {
|
||||||
|
Loading…
Reference in New Issue
Block a user