mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-25 20:42:56 +03:00
Add members to space type for easy management members, and add autojo… (#5612)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
fa3df9981d
commit
8f52663a11
@ -75,8 +75,7 @@ export class TSpace extends TDoc implements Space {
|
||||
@Prop(ArrOf(TypeRef(core.class.Account)), core.string.Owners)
|
||||
owners?: Ref<Account>[]
|
||||
|
||||
@Prop(TypeBoolean(), getEmbeddedLabel('Auto-Join members'))
|
||||
@Hidden() // let's hide it for now
|
||||
@Prop(TypeBoolean(), core.string.AutoJoin)
|
||||
autoJoin?: boolean
|
||||
}
|
||||
|
||||
@ -119,6 +118,12 @@ export class TSpaceType extends TDoc implements SpaceType {
|
||||
|
||||
@Prop(Collection(core.class.Role), core.string.Roles)
|
||||
roles!: CollectionSize<Role>
|
||||
|
||||
@Prop(ArrOf(TypeRef(core.class.Account)), core.string.Members)
|
||||
members!: Arr<Ref<Account>>
|
||||
|
||||
@Prop(TypeBoolean(), core.string.AutoJoin)
|
||||
autoJoin?: boolean
|
||||
}
|
||||
|
||||
@Model(core.class.Role, core.class.AttachedDoc, DOMAIN_MODEL)
|
||||
|
@ -89,6 +89,14 @@ export function createModel (builder: Builder): void {
|
||||
}
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverContact.trigger.OnSpaceTypeMembers,
|
||||
txMatch: {
|
||||
objectClass: core.class.SpaceType,
|
||||
_class: core.class.TxUpdateDoc
|
||||
}
|
||||
})
|
||||
|
||||
builder.mixin(
|
||||
contact.templateField.CurrentEmployeeName,
|
||||
templates.class.TemplateField,
|
||||
|
@ -58,6 +58,8 @@
|
||||
"DeleteObjectDescription": "Grants users ability to delete objects in the space",
|
||||
"ForbidDeleteObjectDescription": "Forbid users deleting objects in the space",
|
||||
"UpdateSpaceDescription": "Grants users ability to update the space",
|
||||
"ArchiveSpaceDescription": "Grants users ability to archive the space"
|
||||
"ArchiveSpaceDescription": "Grants users ability to archive the space",
|
||||
"AutoJoin": "Auto join",
|
||||
"AutoJoinDescr": "Automatically join new employees to this space"
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,21 @@
|
||||
"StatusCategory": "Categoría de estado",
|
||||
"Account": "Cuenta",
|
||||
"Rank": "Rango",
|
||||
"Owners": "Propietarios"
|
||||
"Owners": "Propietarios",
|
||||
"Permission": "Permiso",
|
||||
"CreateObject": "Crear objeto",
|
||||
"UpdateObject": "Actualizar objeto",
|
||||
"DeleteObject": "Eliminar objeto",
|
||||
"ForbidDeleteObject": "Prohibir eliminar objeto",
|
||||
"UpdateSpace": "Actualizar espacio",
|
||||
"ArchiveSpace": "Archivar espacio",
|
||||
"CreateObjectDescription": "Concede a los usuarios la capacidad de crear objetos en el espacio",
|
||||
"UpdateObjectDescription": "Concede a los usuarios la capacidad de actualizar objetos en el espacio",
|
||||
"DeleteObjectDescription": "Concede a los usuarios la capacidad de eliminar objetos en el espacio",
|
||||
"ForbidDeleteObjectDescription": "Prohíbe a los usuarios eliminar objetos en el espacio",
|
||||
"UpdateSpaceDescription": "Concede a los usuarios la capacidad de actualizar el espacio",
|
||||
"ArchiveSpaceDescription": "Concede a los usuarios la capacidad de archivar el espacio",
|
||||
"AutoJoin": "Auto unirse",
|
||||
"AutoJoinDescr": "Unirse automáticamente a los nuevos empleados a este espacio"
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,21 @@
|
||||
"StatusCategory": "Categoria de estado",
|
||||
"Account": "Conta",
|
||||
"Rank": "Ranking",
|
||||
"Owners": "Proprietários"
|
||||
"Owners": "Proprietários",
|
||||
"Permission": "Permissão",
|
||||
"CreateObject": "Criar objeto",
|
||||
"UpdateObject": "Atualizar objeto",
|
||||
"DeleteObject": "Apagar objeto",
|
||||
"ForbidDeleteObject": "Proibir apagar objeto",
|
||||
"UpdateSpace": "Atualizar espaço",
|
||||
"ArchiveSpace": "Arquivar espaço",
|
||||
"CreateObjectDescription": "Concede aos usuários a capacidade de criar objetos no espaço",
|
||||
"UpdateObjectDescription": "Concede aos usuários a capacidade de atualizar objetos no espaço",
|
||||
"DeleteObjectDescription": "Concede aos usuários a capacidade de apagar objetos no espaço",
|
||||
"ForbidDeleteObjectDescription": "Proíbe aos usuários a capacidade de apagar objetos no espaço",
|
||||
"UpdateSpaceDescription": "Concede aos usuários a capacidade de atualizar o espaço",
|
||||
"ArchiveSpaceDescription": "Concede aos usuários a capacidade de arquivar o espaço",
|
||||
"AutoJoin": "Auto adesão",
|
||||
"AutoJoinDescr": "Adesão automática de novos funcionários a este espaço"
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,8 @@
|
||||
"DeleteObjectDescription": "Дает пользователям разрешение удалять объекты в пространстве",
|
||||
"ForbidDeleteObjectDescription": "Запрещает пользователям удалять объекты в пространстве",
|
||||
"UpdateSpaceDescription": "Дает пользователям разрешение обновлять пространство",
|
||||
"ArchiveSpaceDescription": "Дает пользователям разрешение архивировать пространство"
|
||||
"ArchiveSpaceDescription": "Дает пользователям разрешение архивировать пространство",
|
||||
"AutoJoin": "Автоприсоединение",
|
||||
"AutoJoinDescr": "Автоматически присоединять новых сотрудников к этому пространству"
|
||||
}
|
||||
}
|
||||
|
@ -401,6 +401,8 @@ export interface SpaceType extends Doc {
|
||||
name: string
|
||||
shortDescription?: string
|
||||
descriptor: Ref<SpaceTypeDescriptor>
|
||||
members?: Ref<Account>[] // this members will be added automatically to new space, also change this fiield will affect existing spaces
|
||||
autoJoin?: boolean // if true, all new users will be added to space automatically
|
||||
targetClass: Ref<Class<Space>> // A dynamic mixin for Spaces to hold custom attributes and roles assignment of the space type
|
||||
roles: CollectionSize<Role>
|
||||
}
|
||||
|
@ -230,7 +230,9 @@ export default plugin(coreId, {
|
||||
DeleteObjectDescription: '' as IntlString,
|
||||
ForbidDeleteObjectDescription: '' as IntlString,
|
||||
UpdateSpaceDescription: '' as IntlString,
|
||||
ArchiveSpaceDescription: '' as IntlString
|
||||
ArchiveSpaceDescription: '' as IntlString,
|
||||
AutoJoin: '' as IntlString,
|
||||
AutoJoinDescr: '' as IntlString
|
||||
},
|
||||
descriptor: {
|
||||
SpacesType: '' as Ref<SpaceTypeDescriptor>
|
||||
|
@ -17,6 +17,7 @@
|
||||
import { LabelAndProps } from '../types'
|
||||
import { tooltip } from '../tooltips'
|
||||
|
||||
export let id: string | undefined = undefined
|
||||
export let on: boolean = false
|
||||
export let disabled: boolean = false
|
||||
export let showTooltip: LabelAndProps | undefined = undefined
|
||||
@ -24,7 +25,7 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<label class="toggle" use:tooltip={showTooltip}>
|
||||
<label {id} class="toggle" use:tooltip={showTooltip} class:disabled>
|
||||
<input
|
||||
class="chBox"
|
||||
type="checkbox"
|
||||
@ -47,6 +48,12 @@
|
||||
vertical-align: middle;
|
||||
font-size: inherit;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.chBox {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
@ -67,9 +74,6 @@
|
||||
background: var(--theme-toggle-on-sw-color);
|
||||
}
|
||||
}
|
||||
&:not(:disabled) + .toggle-switch {
|
||||
cursor: pointer;
|
||||
}
|
||||
&:disabled + .toggle-switch {
|
||||
filter: grayscale(70%);
|
||||
&:before {
|
||||
|
@ -25,7 +25,7 @@
|
||||
|
||||
export let label: IntlString
|
||||
export let value: Ref<Account>[]
|
||||
export let onChange: ((refs: Ref<Account>[]) => void) | undefined
|
||||
export let onChange: ((refs: Ref<Account>[]) => void | Promise<void>) | undefined
|
||||
export let readonly = false
|
||||
export let kind: ButtonKind = 'link'
|
||||
export let size: ButtonSize = 'large'
|
||||
|
@ -51,7 +51,6 @@ export default mergeIds(contactId, contact, {
|
||||
CopyToClipboard: '' as IntlString,
|
||||
ViewFullProfile: '' as IntlString,
|
||||
Member: '' as IntlString,
|
||||
Members: '' as IntlString,
|
||||
NoMembers: '' as IntlString,
|
||||
AddMember: '' as IntlString,
|
||||
KickEmployee: '' as IntlString,
|
||||
|
@ -263,7 +263,8 @@ export const contactPlugin = plugin(contactId, {
|
||||
Position: '' as IntlString,
|
||||
For: '' as IntlString,
|
||||
SelectUsers: '' as IntlString,
|
||||
AddGuest: '' as IntlString
|
||||
AddGuest: '' as IntlString,
|
||||
Members: '' as IntlString
|
||||
},
|
||||
viewlet: {
|
||||
TableMember: '' as Ref<Viewlet>,
|
||||
|
@ -118,6 +118,7 @@
|
||||
private: isPrivate,
|
||||
members,
|
||||
owners,
|
||||
autoJoin,
|
||||
archived: false,
|
||||
icon,
|
||||
color
|
||||
@ -146,6 +147,9 @@
|
||||
if (teamspaceData.color !== teamspace?.color) {
|
||||
update.color = teamspaceData.color
|
||||
}
|
||||
if (teamspaceData.autoJoin !== teamspace?.autoJoin) {
|
||||
update.autoJoin = teamspaceData.autoJoin
|
||||
}
|
||||
if (teamspaceData.members.length !== teamspace?.members.length) {
|
||||
update.members = teamspaceData.members
|
||||
} else {
|
||||
@ -236,6 +240,7 @@
|
||||
}
|
||||
|
||||
function handleMembersChanged (newMembers: Ref<Account>[]): void {
|
||||
membersChanged = true
|
||||
// If a member was removed we need to remove it from any roles assignments as well
|
||||
const newMembersSet = new Set(newMembers)
|
||||
const removedMembersSet = new Set(members.filter((m) => !newMembersSet.has(m)))
|
||||
@ -257,6 +262,21 @@
|
||||
rolesAssignment[roleId] = newMembers
|
||||
}
|
||||
|
||||
let autoJoin = teamspace?.autoJoin ?? spaceType?.autoJoin ?? false
|
||||
|
||||
$: setDefaultMembers(spaceType)
|
||||
|
||||
let membersChanged: boolean = false
|
||||
|
||||
function setDefaultMembers (typeType: SpaceType | undefined): void {
|
||||
if (typeType === undefined) return
|
||||
if (membersChanged) return
|
||||
if (teamspace !== undefined) return
|
||||
autoJoin = typeType.autoJoin ?? false
|
||||
if (typeType.members === undefined || typeType.members.length === 0) return
|
||||
members = typeType.members
|
||||
}
|
||||
|
||||
$: canSave =
|
||||
name.trim().length > 0 &&
|
||||
!(members.length === 0 && isPrivate) &&
|
||||
@ -366,7 +386,7 @@
|
||||
<Label label={presentation.string.MakePrivate} />
|
||||
<span><Label label={presentation.string.MakePrivateDescription} /></span>
|
||||
</div>
|
||||
<Toggle bind:on={isPrivate} disabled={!isPrivate && members.length === 0} />
|
||||
<Toggle id={'teamspace-private'} bind:on={isPrivate} disabled={!isPrivate && members.length === 0} />
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row">
|
||||
@ -383,6 +403,14 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header withDesciption">
|
||||
<Label label={core.string.AutoJoin} />
|
||||
<span><Label label={core.string.AutoJoinDescr} /></span>
|
||||
</div>
|
||||
<Toggle bind:on={autoJoin} />
|
||||
</div>
|
||||
|
||||
{#each roles as role}
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header">
|
||||
|
@ -27,7 +27,7 @@
|
||||
import lead, { Funnel } from '@hcengineering/lead'
|
||||
import presentation, { getClient, SpaceCreateCard } from '@hcengineering/presentation'
|
||||
import task, { ProjectType } from '@hcengineering/task'
|
||||
import ui, { Component, EditBox, Grid, Label, ToggleWithLabel } from '@hcengineering/ui'
|
||||
import ui, { Component, EditBox, Label, Toggle, ToggleWithLabel } from '@hcengineering/ui'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
@ -99,6 +99,7 @@
|
||||
private: isPrivate,
|
||||
archived: false,
|
||||
members,
|
||||
autoJoin,
|
||||
owners,
|
||||
type: typeId
|
||||
})
|
||||
@ -111,7 +112,11 @@
|
||||
if (isNew) {
|
||||
await createFunnel()
|
||||
} else if (funnel !== undefined && spaceType?.targetClass !== undefined) {
|
||||
await client.diffUpdate<Funnel>(funnel, { name, description, members, owners, private: isPrivate }, Date.now())
|
||||
await client.diffUpdate<Funnel>(
|
||||
funnel,
|
||||
{ name, description, members, owners, private: isPrivate, autoJoin },
|
||||
Date.now()
|
||||
)
|
||||
|
||||
if (!deepEqual(rolesAssignment, getRolesAssignment())) {
|
||||
await client.updateMixin(
|
||||
@ -133,6 +138,7 @@
|
||||
}
|
||||
|
||||
function handleMembersChanged (newMembers: Ref<Account>[]): void {
|
||||
membersChanged = true
|
||||
// If a member was removed we need to remove it from any roles assignments as well
|
||||
const newMembersSet = new Set(newMembers)
|
||||
const removedMembersSet = new Set(members.filter((m) => !newMembersSet.has(m)))
|
||||
@ -156,6 +162,21 @@
|
||||
|
||||
$: canSave =
|
||||
name.trim().length > 0 && members.length > 0 && owners.length > 0 && owners.some((o) => members.includes(o))
|
||||
|
||||
let autoJoin = funnel?.autoJoin ?? spaceType?.autoJoin ?? false
|
||||
|
||||
$: setDefaultMembers(spaceType)
|
||||
|
||||
let membersChanged: boolean = false
|
||||
|
||||
function setDefaultMembers (typeType: SpaceType | undefined): void {
|
||||
if (typeType === undefined) return
|
||||
if (membersChanged) return
|
||||
if (funnel !== undefined) return
|
||||
autoJoin = typeType.autoJoin ?? false
|
||||
if (typeType.members === undefined || typeType.members.length === 0) return
|
||||
members = typeType.members
|
||||
}
|
||||
</script>
|
||||
|
||||
<SpaceCreateCard
|
||||
@ -167,27 +188,37 @@
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<Grid column={1} rowGap={1.5}>
|
||||
<div class="antiGrid-row">
|
||||
<EditBox label={leadRes.string.FunnelName} bind:value={name} placeholder={leadRes.string.FunnelName} autoFocus />
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row">
|
||||
<ToggleWithLabel
|
||||
label={presentation.string.MakePrivate}
|
||||
description={presentation.string.MakePrivateDescription}
|
||||
bind:on={isPrivate}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header">
|
||||
<Label label={task.string.ProjectType} />
|
||||
</div>
|
||||
<Component
|
||||
is={task.component.ProjectTypeSelector}
|
||||
disabled={!isNew}
|
||||
props={{
|
||||
descriptors: [leadRes.descriptors.FunnelType],
|
||||
type: typeId,
|
||||
kind: 'regular',
|
||||
size: 'large',
|
||||
disabled: funnel !== undefined
|
||||
}}
|
||||
on:change={(evt) => {
|
||||
typeId = evt.detail
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</div>
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header">
|
||||
<Label label={core.string.Owners} />
|
||||
@ -201,7 +232,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row mt-4">
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header">
|
||||
<Label label={leadRes.string.Members} />
|
||||
</div>
|
||||
@ -214,6 +245,13 @@
|
||||
size={'large'}
|
||||
/>
|
||||
</div>
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header withDesciption">
|
||||
<Label label={core.string.AutoJoin} />
|
||||
<span><Label label={core.string.AutoJoinDescr} /></span>
|
||||
</div>
|
||||
<Toggle bind:on={autoJoin} />
|
||||
</div>
|
||||
|
||||
{#each roles as role}
|
||||
<div class="antiGrid-row">
|
||||
|
@ -40,6 +40,8 @@
|
||||
EditBox,
|
||||
FocusHandler,
|
||||
IconAttachment,
|
||||
Label,
|
||||
Toggle,
|
||||
createFocusManager,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
@ -61,13 +63,25 @@
|
||||
let issueTemplates: IssueTemplate[] = []
|
||||
let fullDescription: string = ''
|
||||
|
||||
let members = [getCurrentAccount()._id]
|
||||
let membersChanged: boolean = false
|
||||
|
||||
$: setDefaultMembers(typeType)
|
||||
|
||||
function setDefaultMembers (typeType: ProjectType | undefined): void {
|
||||
if (typeType === undefined) return
|
||||
if (membersChanged) return
|
||||
if (typeType.members === undefined || typeType.members.length === 0) return
|
||||
members = typeType.members
|
||||
}
|
||||
|
||||
export let company: Ref<Organization> | undefined
|
||||
export let preserveCompany: boolean = false
|
||||
|
||||
let vacancyData: Data<VacancyClass> = {
|
||||
archived: false,
|
||||
description: '',
|
||||
members: [getCurrentAccount()._id],
|
||||
members,
|
||||
name: '',
|
||||
number: 0,
|
||||
private: false,
|
||||
@ -219,7 +233,8 @@
|
||||
archived: false,
|
||||
number: (incResult as any).object.sequence,
|
||||
company,
|
||||
members: [getCurrentAccount()._id],
|
||||
members,
|
||||
autoJoin: typeType.autoJoin ?? false,
|
||||
owners: [getCurrentAccount()._id],
|
||||
type: typeId
|
||||
},
|
||||
@ -365,6 +380,16 @@
|
||||
extraProps={{ showNavigate: false }}
|
||||
/>
|
||||
|
||||
<AccountArrayEditor
|
||||
bind:value={members}
|
||||
label={contact.string.Members}
|
||||
onChange={() => {
|
||||
membersChanged = true
|
||||
}}
|
||||
kind={'regular'}
|
||||
size={'large'}
|
||||
/>
|
||||
|
||||
{#each roles as role}
|
||||
<AccountArrayEditor
|
||||
value={rolesAssignment?.[role._id] ?? []}
|
||||
|
@ -13,9 +13,11 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { type SpaceType, type SpaceTypeDescriptor } from '@hcengineering/core'
|
||||
import { ButtonIcon, IconSquareExpand, ModernButton, ModernEditbox, TextArea } from '@hcengineering/ui'
|
||||
import contact from '@hcengineering/contact'
|
||||
import { AccountArrayEditor } from '@hcengineering/contact-resources'
|
||||
import core, { Account, Ref, type SpaceType, type SpaceTypeDescriptor } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { ButtonIcon, IconSquareExpand, Label, ModernButton, ModernEditbox, TextArea, Toggle } from '@hcengineering/ui'
|
||||
|
||||
import settingRes from '../../../plugin'
|
||||
|
||||
@ -50,6 +52,33 @@
|
||||
|
||||
await client.update(type, { [field]: value })
|
||||
}
|
||||
|
||||
async function changeMembers (members: Ref<Account>[]): Promise<void> {
|
||||
if (disabled || type === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const push = new Set<Ref<Account>>(members)
|
||||
const pull = new Set<Ref<Account>>()
|
||||
for (const member of type.members ?? []) {
|
||||
if (!push.has(member)) {
|
||||
pull.add(member)
|
||||
} else {
|
||||
push.delete(member)
|
||||
}
|
||||
}
|
||||
if (push.size === 0 && pull.size === 0) {
|
||||
return
|
||||
}
|
||||
const ops = client.apply(`typeMembers_${type._id}`)
|
||||
for (const pushMem of push) {
|
||||
ops.update(type, { $push: { members: pushMem } })
|
||||
}
|
||||
for (const pullMem of pull) {
|
||||
ops.update(type, { $pull: { members: pullMem } })
|
||||
}
|
||||
await ops.commit()
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if descriptor !== undefined}
|
||||
@ -90,6 +119,24 @@
|
||||
attributeUpdated('shortDescription', shortDescription)
|
||||
}}
|
||||
/>
|
||||
<div class="flex-between">
|
||||
<AccountArrayEditor
|
||||
value={type?.members ?? []}
|
||||
label={contact.string.Members}
|
||||
onChange={changeMembers}
|
||||
readonly={disabled}
|
||||
/>
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
<Label label={core.string.AutoJoin} />
|
||||
<Toggle
|
||||
on={type?.autoJoin ?? false}
|
||||
on:change={(evt) => {
|
||||
attributeUpdated('autoJoin', evt.detail)
|
||||
}}
|
||||
{disabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="extra" />
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -76,7 +76,9 @@
|
||||
let defaultStatus: Ref<IssueStatus> | undefined = project?.defaultIssueStatus
|
||||
let rolesAssignment: RolesAssignment | undefined
|
||||
|
||||
let changeIdentityRef: HTMLElement
|
||||
let typeId: Ref<ProjectType> | undefined = project?.type
|
||||
$: typeType = typeId !== undefined ? $typeStore.get(typeId) : undefined
|
||||
let autoJoin = project?.autoJoin ?? typeType?.autoJoin ?? false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@ -100,6 +102,7 @@
|
||||
members,
|
||||
owners,
|
||||
archived: false,
|
||||
autoJoin,
|
||||
identifier: identifier.toUpperCase(),
|
||||
sequence: 0,
|
||||
defaultAssignee: defaultAssignee ?? undefined,
|
||||
@ -155,6 +158,9 @@
|
||||
if (projectData.defaultTimeReportDay !== project?.defaultTimeReportDay) {
|
||||
update.defaultTimeReportDay = projectData.defaultTimeReportDay
|
||||
}
|
||||
if (projectData.autoJoin !== project?.autoJoin) {
|
||||
update.autoJoin = projectData.autoJoin
|
||||
}
|
||||
if (projectData.members.length !== project?.members.length) {
|
||||
update.members = projectData.members
|
||||
} else {
|
||||
@ -195,8 +201,16 @@
|
||||
close()
|
||||
}
|
||||
|
||||
let typeId: Ref<ProjectType> | undefined = project?.type
|
||||
$: typeType = typeId !== undefined ? $typeStore.get(typeId) : undefined
|
||||
$: setDefaultMembers(typeType)
|
||||
|
||||
function setDefaultMembers (typeType: ProjectType | undefined): void {
|
||||
if (typeType === undefined) return
|
||||
if (membersChanged) return
|
||||
if (project !== undefined) return
|
||||
autoJoin = typeType.autoJoin ?? false
|
||||
if (typeType.members === undefined || typeType.members.length === 0) return
|
||||
members = typeType.members
|
||||
}
|
||||
|
||||
function findTaskTypes (typeId: Ref<SpaceType>): TaskType[] {
|
||||
return Array.from($taskTypeStore.values()).filter(
|
||||
@ -254,6 +268,8 @@
|
||||
dispatch('close', id)
|
||||
}
|
||||
|
||||
let membersChanged: boolean = false
|
||||
|
||||
$: projectsQuery.query(tracker.class.Project, { _id: { $nin: project ? [project._id] : [] } }, (res) => {
|
||||
projectsIdentifiers = new Set(res.map(({ identifier }) => identifier))
|
||||
})
|
||||
@ -296,6 +312,7 @@
|
||||
}
|
||||
|
||||
function handleMembersChanged (newMembers: Ref<Account>[]): void {
|
||||
membersChanged = true
|
||||
// If a member was removed we need to remove it from any roles assignments as well
|
||||
const newMembersSet = new Set(newMembers)
|
||||
const removedMembersSet = new Set(members.filter((m) => !newMembersSet.has(m)))
|
||||
@ -382,7 +399,7 @@
|
||||
<Label label={tracker.string.Identifier} />
|
||||
<span><Label label={tracker.string.UsedInIssueIDs} /></span>
|
||||
</div>
|
||||
<div bind:this={changeIdentityRef} class="padding flex-row-center relative">
|
||||
<div class="padding flex-row-center relative">
|
||||
<EditBox
|
||||
id="project-identifier"
|
||||
bind:value={identifier}
|
||||
@ -483,7 +500,7 @@
|
||||
<Label label={presentation.string.MakePrivate} />
|
||||
<span><Label label={presentation.string.MakePrivateDescription} /></span>
|
||||
</div>
|
||||
<Toggle bind:on={isPrivate} disabled={!isPrivate && members.length === 0} />
|
||||
<Toggle id={'project-private'} bind:on={isPrivate} disabled={!isPrivate && members.length === 0} />
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row">
|
||||
@ -499,6 +516,14 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header withDesciption">
|
||||
<Label label={core.string.AutoJoin} />
|
||||
<span><Label label={core.string.AutoJoinDescr} /></span>
|
||||
</div>
|
||||
<Toggle bind:on={autoJoin} />
|
||||
</div>
|
||||
|
||||
{#each roles as role}
|
||||
<div class="antiGrid-row">
|
||||
<div class="antiGrid-row__header">
|
||||
|
@ -18,7 +18,7 @@
|
||||
import { CheckBox, resizeObserver } from '@hcengineering/ui'
|
||||
import BooleanPresenter from './BooleanPresenter.svelte'
|
||||
|
||||
export let value: boolean
|
||||
export let value: boolean | null | undefined
|
||||
export let withoutUndefined: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@ -31,7 +31,7 @@
|
||||
<BooleanPresenter value={true} />
|
||||
{#if value}
|
||||
<div class="check">
|
||||
<CheckBox checked={value} kind={'primary'} />
|
||||
<CheckBox checked kind={'primary'} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@ -39,9 +39,9 @@
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="menu-item" on:click={() => dispatch('close', 2)}>
|
||||
<BooleanPresenter value={false} />
|
||||
{#if withoutUndefined ? !value : !value}
|
||||
{#if withoutUndefined ? !value : value === false}
|
||||
<div class="check">
|
||||
<CheckBox checked={withoutUndefined ? !value : !value} kind={'primary'} />
|
||||
<CheckBox checked kind={'primary'} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@ -50,9 +50,9 @@
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="menu-item" on:click={() => dispatch('close', 3)}>
|
||||
<BooleanPresenter value={undefined} />
|
||||
{#if value === undefined}
|
||||
{#if value == null}
|
||||
<div class="check">
|
||||
<CheckBox checked={value === undefined} kind={'primary'} />
|
||||
<CheckBox checked kind={'primary'} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -17,7 +17,7 @@
|
||||
import { BooleanIcon, Label } from '@hcengineering/ui'
|
||||
import { getBooleanLabel } from '../utils'
|
||||
|
||||
export let value: any
|
||||
export let value: boolean | null | undefined
|
||||
export let inline: boolean = false
|
||||
</script>
|
||||
|
||||
|
@ -591,7 +591,7 @@ function getLookup (
|
||||
return current !== undefined ? [current, parent, false] : undefined
|
||||
}
|
||||
|
||||
export function getBooleanLabel (value: boolean | undefined): IntlString {
|
||||
export function getBooleanLabel (value: boolean | undefined | null): IntlString {
|
||||
if (value === true) return plugin.string.LabelYes
|
||||
if (value === false) return plugin.string.LabelNo
|
||||
return plugin.string.LabelNA
|
||||
|
@ -29,10 +29,12 @@ import contact, {
|
||||
getName
|
||||
} from '@hcengineering/contact'
|
||||
import core, {
|
||||
Account,
|
||||
Class,
|
||||
Doc,
|
||||
Hierarchy,
|
||||
Ref,
|
||||
SpaceType,
|
||||
Tx,
|
||||
TxCreateDoc,
|
||||
TxMixin,
|
||||
@ -46,6 +48,38 @@ import { getMetadata } from '@hcengineering/platform'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
|
||||
export async function OnSpaceTypeMembers (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = tx as TxUpdateDoc<SpaceType>
|
||||
const result: Tx[] = []
|
||||
const newMember = ctx.operations.$push?.members as Ref<Account>
|
||||
if (newMember !== undefined) {
|
||||
const spaces = await control.findAll(core.class.Space, { type: ctx.objectId })
|
||||
for (const space of spaces) {
|
||||
if (space.members.includes(newMember)) continue
|
||||
const pushTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
|
||||
$push: {
|
||||
members: newMember
|
||||
}
|
||||
})
|
||||
result.push(pushTx)
|
||||
}
|
||||
}
|
||||
const oldMember = ctx.operations.$pull?.members as Ref<Account>
|
||||
if (ctx.operations.$pull?.members !== undefined) {
|
||||
const spaces = await control.findAll(core.class.Space, { type: ctx.objectId })
|
||||
for (const space of spaces) {
|
||||
if (!space.members.includes(oldMember)) continue
|
||||
const pullTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
|
||||
$pull: {
|
||||
members: oldMember
|
||||
}
|
||||
})
|
||||
result.push(pullTx)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export async function OnEmployeeCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const mixinTx = tx as TxMixin<Person, Employee>
|
||||
if (mixinTx.attributes.active !== true) return []
|
||||
@ -305,7 +339,8 @@ export default async () => ({
|
||||
OnEmployeeCreate,
|
||||
OnPersonAccountCreate,
|
||||
OnContactDelete,
|
||||
OnChannelUpdate
|
||||
OnChannelUpdate,
|
||||
OnSpaceTypeMembers
|
||||
},
|
||||
function: {
|
||||
PersonHTMLPresenter: personHTMLPresenter,
|
||||
|
@ -33,7 +33,8 @@ export default plugin(serverContactId, {
|
||||
OnContactDelete: '' as Resource<TriggerFunc>,
|
||||
OnChannelUpdate: '' as Resource<TriggerFunc>,
|
||||
OnEmployeeCreate: '' as Resource<TriggerFunc>,
|
||||
OnPersonAccountCreate: '' as Resource<TriggerFunc>
|
||||
OnPersonAccountCreate: '' as Resource<TriggerFunc>,
|
||||
OnSpaceTypeMembers: '' as Resource<TriggerFunc>
|
||||
},
|
||||
function: {
|
||||
PersonHTMLPresenter: '' as Resource<Presenter>,
|
||||
|
@ -30,7 +30,7 @@ export class DocumentsPage extends CommonPage {
|
||||
readonly inputModalNewTeamspaceDescription = (): Locator =>
|
||||
this.formNewTeamspace().locator('div[id="teamspace-description"] input')
|
||||
|
||||
readonly inputModalNewTeamspacePrivate = (): Locator => this.formNewTeamspace().locator('div.antiGrid label.toggle')
|
||||
readonly inputModalNewTeamspacePrivate = (): Locator => this.formNewTeamspace().locator('[id="teamspace-private"]')
|
||||
readonly buttonModalNewTeamspaceCreate = (): Locator => this.formNewTeamspace().locator('button[type="submit"]')
|
||||
readonly buttonModalEditTeamspaceTitle = (): Locator =>
|
||||
this.formEditTeamspace().locator('div[id="teamspace-title"] input')
|
||||
@ -38,8 +38,7 @@ export class DocumentsPage extends CommonPage {
|
||||
readonly buttonModalEditTeamspaceDescription = (): Locator =>
|
||||
this.formEditTeamspace().locator('div[id="teamspace-description"] input')
|
||||
|
||||
readonly buttonModalEditTeamspacePrivate = (): Locator =>
|
||||
this.formEditTeamspace().locator('div.antiGrid label.toggle')
|
||||
readonly buttonModalEditTeamspacePrivate = (): Locator => this.formEditTeamspace().locator('[id="teamspace-private"]')
|
||||
|
||||
readonly buttonModalEditTeamspaceSave = (): Locator => this.formEditTeamspace().locator('button[type="submit"]')
|
||||
readonly buttonModalEditTeamspaceClose = (): Locator => this.formEditTeamspace().locator('button#card-close')
|
||||
|
@ -16,7 +16,7 @@ export class EditProjectPage extends CommonTrackerPage {
|
||||
this.page.locator('form[id="tracker:string:EditProject"] div[id="project-description"] input')
|
||||
|
||||
buttonChooseIcon = (): Locator => this.page.locator('div.antiGrid-row button.only-icon')
|
||||
buttonMakePrivate = (): Locator => this.page.locator('div.antiGrid-row span.toggle-switch')
|
||||
buttonMakePrivate = (): Locator => this.page.locator('[id="project-private"]')
|
||||
buttonSaveProject = (): Locator => this.page.locator('form[id="tracker:string:EditProject"] button[type="submit"]')
|
||||
buttonIcons = (): Locator => this.page.locator('form[id="view:string:ChooseIcon"] div.float-left > button')
|
||||
buttonSaveIcons = (): Locator =>
|
||||
|
@ -12,7 +12,7 @@ export class NewProjectPage extends CommonTrackerPage {
|
||||
inputIdentifier = (): Locator => this.page.locator('div[id="project-identifier"] input')
|
||||
inputDescription = (): Locator => this.page.locator('div[id="project-description"] input')
|
||||
buttonChooseIcon = (): Locator => this.page.locator('div.antiGrid-row button.only-icon')
|
||||
buttonMakePrivate = (): Locator => this.page.locator('div.antiGrid-row span.toggle-switch')
|
||||
buttonMakePrivate = (): Locator => this.page.locator('[id="project-private"]')
|
||||
buttonCreateProject = (): Locator => this.page.locator('form[id="tracker:string:NewProject"] button[type="submit"]')
|
||||
projectTypeButton = (): Locator =>
|
||||
this.page.locator('div[class*="header"]', { hasText: 'Project type' }).locator('xpath=..').locator('button')
|
||||
|
Loading…
Reference in New Issue
Block a user