diff --git a/models/recruit/src/types.ts b/models/recruit/src/types.ts index c33b39cc64..c6d390be15 100644 --- a/models/recruit/src/types.ts +++ b/models/recruit/src/types.ts @@ -18,7 +18,7 @@ import { Account, IndexKind, type CollaborativeDoc, - type Collection as Array, + type Collection, type Domain, type Markup, type Ref, @@ -28,7 +28,7 @@ import { type Timestamp } from '@hcengineering/core' import { - Collection, + Collection as TypeCollection, Hidden, Index, Mixin, @@ -70,7 +70,9 @@ export class TVacancy extends TProject implements Vacancy { @Index(IndexKind.FullText) fullDescription!: CollaborativeDoc - @Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files }) + @Prop(TypeCollection(attachment.class.Attachment), attachment.string.Attachments, { + shortLabel: attachment.string.Files + }) attachments?: number @Prop(TypeDate(), recruit.string.Due, recruit.icon.Calendar) @@ -83,13 +85,16 @@ export class TVacancy extends TProject implements Vacancy { @Prop(TypeRef(contact.class.Organization), recruit.string.Company, { icon: contact.icon.Company }) company?: Ref - @Prop(Collection(chunter.class.ChatMessage), chunter.string.Comments) + @Prop(TypeCollection(chunter.class.ChatMessage), chunter.string.Comments) comments?: number @Prop(TypeString(), recruit.string.Vacancy) @Index(IndexKind.FullText) @Hidden() number!: number + + @Prop(TypeCollection(survey.class.Poll), survey.string.Polls) + polls?: Collection } @Mixin(recruit.mixin.Candidate, contact.class.Person) @@ -99,7 +104,7 @@ export class TCandidate extends TPerson implements Candidate { @Index(IndexKind.FullText) title?: string - @Prop(Collection(recruit.class.Applicant), recruit.string.Applications, { + @Prop(TypeCollection(recruit.class.Applicant), recruit.string.Applications, { shortLabel: recruit.string.ApplicationsShort }) applications?: number @@ -114,26 +119,29 @@ export class TCandidate extends TPerson implements Candidate { @Index(IndexKind.FullText) source?: string - @Prop(Collection(tags.class.TagReference, recruit.string.SkillLabel), recruit.string.SkillsLabel, { + @Prop(TypeCollection(tags.class.TagReference, recruit.string.SkillLabel), recruit.string.SkillsLabel, { icon: recruit.icon.Skills, schema: '3' }) skills?: number - @Prop(Collection(recruit.class.Review, recruit.string.Review), recruit.string.Reviews) + @Prop(TypeCollection(recruit.class.Review, recruit.string.Review), recruit.string.Reviews) reviews?: number @Prop( - Collection(recruit.class.ApplicantMatch, getEmbeddedLabel('Vacancy match')), + TypeCollection(recruit.class.ApplicantMatch, getEmbeddedLabel('Vacancy match')), getEmbeddedLabel('Vacancy Matches') ) vacancyMatch?: number + + @Prop(TypeCollection(survey.class.Poll), survey.string.Polls) + polls?: Collection } @Mixin(recruit.mixin.VacancyList, contact.class.Organization) @UX(recruit.string.VacancyList, recruit.icon.RecruitApplication, 'CM', 'name') export class TVacancyList extends TOrganization implements VacancyList { - @Prop(Collection(recruit.class.Vacancy), recruit.string.Vacancies) + @Prop(TypeCollection(recruit.class.Vacancy), recruit.string.Vacancies) vacancies!: number } @@ -162,8 +170,8 @@ export class TApplicant extends TTask implements Applicant { @Index(IndexKind.Indexed) declare status: Ref - @Prop(Collection(survey.class.Poll), survey.string.Polls) - polls?: Array + @Prop(TypeCollection(survey.class.Poll), survey.string.Polls) + polls?: Collection } @Model(recruit.class.ApplicantMatch, core.class.AttachedDoc, DOMAIN_TASK) @@ -211,7 +219,7 @@ export class TReview extends TEvent implements Review { @Prop(TypeRef(contact.class.Organization), recruit.string.Company, { icon: contact.icon.Company }) company?: Ref - @Prop(Collection(recruit.class.Opinion), recruit.string.Opinions) + @Prop(TypeCollection(recruit.class.Opinion), recruit.string.Opinions) opinions?: number } @@ -225,10 +233,12 @@ export class TOpinion extends TAttachedDoc implements Opinion { @Prop(TypeRef(recruit.class.Review), recruit.string.Review) declare attachedTo: Ref - @Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files }) + @Prop(TypeCollection(attachment.class.Attachment), attachment.string.Attachments, { + shortLabel: attachment.string.Files + }) attachments?: number - @Prop(Collection(chunter.class.ChatMessage), chunter.string.Comments) + @Prop(TypeCollection(chunter.class.ChatMessage), chunter.string.Comments) comments?: number @Prop(TypeMarkup(), recruit.string.Description) diff --git a/models/survey/package.json b/models/survey/package.json index fa69730e8f..7363bd615d 100644 --- a/models/survey/package.json +++ b/models/survey/package.json @@ -38,6 +38,7 @@ "@hcengineering/platform": "^0.6.11", "@hcengineering/survey": "^0.6.0", "@hcengineering/survey-resources": "^0.6.0", - "@hcengineering/ui": "^0.6.15" + "@hcengineering/ui": "^0.6.15", + "@hcengineering/view": "^0.6.13" } } diff --git a/models/survey/src/index.ts b/models/survey/src/index.ts index 4db5607ef7..0a8d5650fb 100644 --- a/models/survey/src/index.ts +++ b/models/survey/src/index.ts @@ -18,7 +18,7 @@ import { AccountRole } from '@hcengineering/core' import { type Builder } from '@hcengineering/model' import core from '@hcengineering/model-core' import chunter from '@hcengineering/model-chunter' -import view, { type Viewlet } from '@hcengineering/model-view' +import view, { createAction, type Viewlet } from '@hcengineering/model-view' import workbench from '@hcengineering/model-workbench' import { surveyId } from '@hcengineering/survey' import { TPoll, TSurvey } from './types' @@ -79,6 +79,14 @@ export function createModel (builder: Builder): void { survey.viewlet.TableSurvey ) + builder.mixin(survey.class.Survey, core.class.Class, view.mixin.ObjectTitle, { + titleProvider: survey.function.SurveyTitleProvider + }) + + builder.mixin(survey.class.Survey, core.class.Class, view.mixin.LinkProvider, { + encode: survey.function.GetSurveyLink + }) + builder.mixin(survey.class.Survey, core.class.Class, view.mixin.ObjectPanel, { component: survey.component.EditSurveyPanel }) @@ -100,20 +108,58 @@ export function createModel (builder: Builder): void { { attachTo: survey.class.Poll, descriptor: view.viewlet.Table, - config: ['', 'modifiedOn'], + config: ['', 'isCompleted', 'modifiedOn'], configOptions: { - hiddenKeys: ['name', 'survey', 'results'], + hiddenKeys: ['name', 'survey', 'questions'], sortable: true } }, survey.viewlet.TablePoll ) + builder.mixin(survey.class.Poll, core.class.Class, view.mixin.ObjectTitle, { + titleProvider: survey.function.PollTitleProvider + }) + + builder.mixin(survey.class.Poll, core.class.Class, view.mixin.LinkProvider, { + encode: survey.function.GetPollLink + }) + + builder.mixin(survey.class.Poll, core.class.Class, view.mixin.ObjectPanel, { + component: survey.component.EditPollPanel + }) + builder.mixin(survey.class.Poll, core.class.Class, view.mixin.ObjectPresenter, { presenter: survey.component.PollPresenter }) + builder.mixin(survey.class.Poll, core.class.Class, activity.mixin.ActivityDoc, {}) + + builder.createDoc(activity.class.ActivityExtension, core.space.Model, { + ofClass: survey.class.Poll, + components: { input: chunter.component.ChatMessageInput } + }) + builder.mixin(survey.class.Poll, core.class.Class, view.mixin.CollectionEditor, { editor: survey.component.PollCollection }) + + createAction( + builder, + { + action: survey.actionImpl.DeletePoll, + label: workbench.string.Delete, + icon: view.icon.Delete, + input: 'any', + category: survey.category.Survey, + target: survey.class.Poll, + context: { + mode: ['context', 'browser'], + group: 'remove' + }, + visibilityTester: view.function.CanDeleteObject, + override: [view.action.Delete] + }, + survey.action.DeletePoll + ) } diff --git a/models/survey/src/types.ts b/models/survey/src/types.ts index f71a2e10ef..effa6b10ad 100644 --- a/models/survey/src/types.ts +++ b/models/survey/src/types.ts @@ -14,7 +14,18 @@ // import { IndexKind, type Domain, type Ref } from '@hcengineering/core' -import { ArrOf, Hidden, Index, Model, Prop, TypeRecord, TypeRef, TypeString, UX } from '@hcengineering/model' +import { + ArrOf, + Hidden, + Index, + Model, + Prop, + TypeBoolean, + TypeRecord, + TypeRef, + TypeString, + UX +} from '@hcengineering/model' import core, { TAttachedDoc, TDoc } from '@hcengineering/model-core' import { getEmbeddedLabel } from '@hcengineering/platform' import { type Poll, type Question, type Survey } from '@hcengineering/survey' @@ -48,7 +59,10 @@ export class TPoll extends TAttachedDoc implements Poll { @Prop(TypeString(), survey.string.Prompt) prompt!: string - @Prop(ArrOf(TypeRecord()), getEmbeddedLabel('Answers')) + @Prop(ArrOf(TypeRecord()), getEmbeddedLabel('Questions')) @Hidden() - results?: { question: string, answer: string[] }[] + questions?: Question[] + + @Prop(TypeBoolean(), survey.string.Completed) + isCompleted?: boolean } diff --git a/plugins/recruit-resources/package.json b/plugins/recruit-resources/package.json index da97c18ba2..b64b470330 100644 --- a/plugins/recruit-resources/package.json +++ b/plugins/recruit-resources/package.json @@ -54,6 +54,8 @@ "@hcengineering/presentation": "^0.6.3", "@hcengineering/recruit": "^0.6.29", "@hcengineering/rekoni": "^0.6.0", + "@hcengineering/survey": "^0.6.0", + "@hcengineering/survey-resources": "^0.6.0", "@hcengineering/tags": "^0.6.16", "@hcengineering/tags-resources": "^0.6.0", "@hcengineering/task": "^0.6.20", diff --git a/plugins/recruit-resources/src/components/EditVacancy.svelte b/plugins/recruit-resources/src/components/EditVacancy.svelte index 1c367c8364..8ccc9d8907 100644 --- a/plugins/recruit-resources/src/components/EditVacancy.svelte +++ b/plugins/recruit-resources/src/components/EditVacancy.svelte @@ -21,6 +21,7 @@ import { getResource } from '@hcengineering/platform' import presentation, { createQuery, getClient } from '@hcengineering/presentation' import { Vacancy } from '@hcengineering/recruit' + import survey from '@hcengineering/survey' import tracker from '@hcengineering/tracker' import { Button, Component, EditBox, IconMixin, IconMoreH, Label } from '@hcengineering/ui' import view from '@hcengineering/view' @@ -202,6 +203,9 @@
+
+ +
diff --git a/plugins/recruit-resources/src/utils.ts b/plugins/recruit-resources/src/utils.ts index e22880226c..9cc03125bb 100644 --- a/plugins/recruit-resources/src/utils.ts +++ b/plugins/recruit-resources/src/utils.ts @@ -10,6 +10,8 @@ import { type Vacancy, type VacancyList } from '@hcengineering/recruit' +import { type Poll } from '@hcengineering/survey' +import { generatePollLocation } from '@hcengineering/survey-resources' import { getCurrentResolvedLocation, getPanelURI, type Location, type ResolvedLocation } from '@hcengineering/ui' import view from '@hcengineering/view' import { accessDeniedStore } from '@hcengineering/view-resources' @@ -34,6 +36,10 @@ export async function resolveLocation (loc: Location): Promise) + } + const shortLink = loc.path[3] // shortlink diff --git a/plugins/recruit/src/types.ts b/plugins/recruit/src/types.ts index f75e34e5d5..5257bf7735 100644 --- a/plugins/recruit/src/types.ts +++ b/plugins/recruit/src/types.ts @@ -38,6 +38,7 @@ export interface Vacancy extends Project { company?: Ref comments?: number number: number + polls?: Collection } /** @public */ @@ -54,6 +55,7 @@ export interface Candidate extends Person { source?: string skills?: number reviews?: number + polls?: Collection } /** @public */ diff --git a/plugins/survey-assets/assets/icons.svg b/plugins/survey-assets/assets/icons.svg index d6ad06e5e4..0cad8bcca2 100644 --- a/plugins/survey-assets/assets/icons.svg +++ b/plugins/survey-assets/assets/icons.svg @@ -24,4 +24,17 @@ + + + + + + + + + + + + + diff --git a/plugins/survey-assets/lang/en.json b/plugins/survey-assets/lang/en.json index 9085e53678..1bd840ca02 100644 --- a/plugins/survey-assets/lang/en.json +++ b/plugins/survey-assets/lang/en.json @@ -6,9 +6,12 @@ "Application": "Survey", "Close": "Close", "Control": "Control", + "Completed": "Completed", "CreatePoll": "Make a survey", "CreateSurvey": "Survey", "DeleteOption": "Delete Option", + "DeletePoll": "Delete Survey", + "DeletePollConfirm": "Are you sure you want to delete the survey?", "DeleteQuestion": "Delete Question", "DeleteQuestionConfirm": "Are you sure you want to delete the question?", "Name": "Title", @@ -27,12 +30,19 @@ "QuestionKindOptions": "Select several options", "QuestionIsMandatory": "Is mandatory", "QuestionHasCustomOption": "Has custom option", - "QuestionOptionPlaceholder": "Add an option", "QuestionPlaceholder": "Add a question", - "QuestionEmptyPlaceholder": "Provide question text", + "QuestionPlaceholderEmpty": "Provide question text", + "QuestionPlaceholderOption": "Add an option", + "QuestionTooltipMandatory": "Answer is required", + "QuestionTooltipCustomOption": "User can provide their own option", "Survey": "Survey Form", "Surveys": "Survey Forms", + "SurveyEdit": "Edit Form", "SurveyPreview": "Preview Form", - "SurveySubmit": "Submit" + "SurveySubmit": "Submit", + "SurveySubmitConfirm": "You will not be able to change answers after that. Are you sure you want to submit now?", + "ValidateFail": "Some required questions are not answered", + "ValidateInfo": "This is how the form will look like for an user. Try to type answers and select options to test your survey. A green icon in the header above shows the form is filled properly", + "ValidateOk": "Form is filled correctly" } } diff --git a/plugins/survey-assets/lang/ru.json b/plugins/survey-assets/lang/ru.json index 728255eeec..ba7663179d 100644 --- a/plugins/survey-assets/lang/ru.json +++ b/plugins/survey-assets/lang/ru.json @@ -6,9 +6,12 @@ "Application": "Опросник", "Close": "Закрыть", "Control": "Управление", + "Completed": "Завершен", "CreatePoll": "Провести опрос", "CreateSurvey": "Анкета", "DeleteOption": "Удалить вариант", + "DeletePoll": "Удалить опрос", + "DeletePollConfirm": "Уверены, что хотите удалить опрос?", "DeleteQuestion": "Удалить вопрос", "DeleteQuestionConfirm": "Уверены, что хотите удалить вопрос?", "Name": "Заголовок", @@ -27,12 +30,19 @@ "QuestionKindOptions": "Выбор нескольких вариантов", "QuestionIsMandatory": "Обязательный вопрос", "QuestionHasCustomOption": "Есть свой вариант", - "QuestionOptionPlaceholder": "Добавить вариант", "QuestionPlaceholder": "Добавить вопрос", - "QuestionEmptyPlaceholder": "Введите текст вопроса", + "QuestionPlaceholderEmpty": "Введите текст вопроса", + "QuestionPlaceholderOption": "Добавить вариант", + "QuestionTooltipMandatory": "Ответ обязателен", + "QuestionTooltipCustomOption": "Пользователь может предложить свой вариант", "Survey": "Анкета", "Surveys": "Анкеты", + "SurveyEdit": "Редактировать форму", "SurveyPreview": "Просмотр формы", - "SurveySubmit": "Отправить" + "SurveySubmit": "Завершить", + "SurveySubmitConfirm": "После этого вы больше не сможете изменять ответы. Вы уверены, что хотите завершить опрос сейчас?", + "ValidateFail": "Нет ответов на некоторые обязательные вопросы", + "ValidateInfo": "Так будет выглядеть форма для пользователя. Для проверки анкеты попробуйте вводить ответы и выбирать варианты. Зеленый значок в заголовке покажет, что форма заполнена правильно", + "ValidateOk": "Форма заполнена правильно" } } diff --git a/plugins/survey-assets/src/index.ts b/plugins/survey-assets/src/index.ts index 5c1fa77c39..865fc8e649 100644 --- a/plugins/survey-assets/src/index.ts +++ b/plugins/survey-assets/src/index.ts @@ -19,13 +19,17 @@ import survey, { surveyId } from '@hcengineering/survey' const icons = require('../assets/icons.svg') as string // eslint-disable-line loadMetadata(survey.icon, { Application: `${icons}#application`, + Info: `${icons}#info`, Poll: `${icons}#page`, Question: `${icons}#question`, QuestionKindString: `${icons}#textline`, QuestionKindOption: `${icons}#radio`, QuestionKindOptions: `${icons}#checkbox`, Survey: `${icons}#application`, + Submit: `${icons}#submit`, QuestionIsMandatory: `${icons}#asterisk`, - QuestionHasCustomOption: `${icons}#star` + QuestionHasCustomOption: `${icons}#star`, + ValidateFail: `${icons}#validate-fail`, + ValidateOk: `${icons}#validate-ok` }) addStringsLoader(surveyId, async (lang: string) => await import(`../lang/${lang}.json`)) diff --git a/plugins/survey-resources/src/components/EditPoll.svelte b/plugins/survey-resources/src/components/EditPoll.svelte new file mode 100644 index 0000000000..fc15c188b2 --- /dev/null +++ b/plugins/survey-resources/src/components/EditPoll.svelte @@ -0,0 +1,90 @@ + + + +
+ {#if hasText(object.prompt)} +
+ + {object.prompt} + +
+ {/if} + {#each object.questions ?? [] as question, index} + {#if isQuestionValid(question)} +
+ +
+ {/if} + {/each} +
+ + diff --git a/plugins/survey-resources/src/components/EditPollPanel.svelte b/plugins/survey-resources/src/components/EditPollPanel.svelte new file mode 100644 index 0000000000..8cacdaa163 --- /dev/null +++ b/plugins/survey-resources/src/components/EditPollPanel.svelte @@ -0,0 +1,114 @@ + + + +{#if object} + { + dispatch('close') + }} + withoutInput={readonly} + > + + {#if !embedded}{/if} + +
{object.name}
+
+
+ + + {#if !readonly} + {#if !(object.isCompleted ?? false)} +