Inventory (#823)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-01-14 15:20:31 +06:00 committed by GitHub
parent cd6148436f
commit 09d3cd4238
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 2191 additions and 63 deletions

View File

@ -36,6 +36,9 @@ specifiers:
'@rush-temp/gmail-resources': file:./projects/gmail-resources.tgz
'@rush-temp/image-cropper': file:./projects/image-cropper.tgz
'@rush-temp/image-cropper-resources': file:./projects/image-cropper-resources.tgz
'@rush-temp/inventory': file:./projects/inventory.tgz
'@rush-temp/inventory-assets': file:./projects/inventory-assets.tgz
'@rush-temp/inventory-resources': file:./projects/inventory-resources.tgz
'@rush-temp/lead': file:./projects/lead.tgz
'@rush-temp/lead-assets': file:./projects/lead-assets.tgz
'@rush-temp/lead-resources': file:./projects/lead-resources.tgz
@ -51,6 +54,7 @@ specifiers:
'@rush-temp/model-core': file:./projects/model-core.tgz
'@rush-temp/model-demo': file:./projects/model-demo.tgz
'@rush-temp/model-gmail': file:./projects/model-gmail.tgz
'@rush-temp/model-inventory': file:./projects/model-inventory.tgz
'@rush-temp/model-lead': file:./projects/model-lead.tgz
'@rush-temp/model-recruit': file:./projects/model-recruit.tgz
'@rush-temp/model-rig': file:./projects/model-rig.tgz
@ -217,6 +221,9 @@ dependencies:
'@rush-temp/gmail-resources': file:projects/gmail-resources.tgz_096c09b0b673a57c275d9767a12070b1
'@rush-temp/image-cropper': file:projects/image-cropper.tgz
'@rush-temp/image-cropper-resources': file:projects/image-cropper-resources.tgz_096c09b0b673a57c275d9767a12070b1
'@rush-temp/inventory': file:projects/inventory.tgz
'@rush-temp/inventory-assets': file:projects/inventory-assets.tgz
'@rush-temp/inventory-resources': file:projects/inventory-resources.tgz_096c09b0b673a57c275d9767a12070b1
'@rush-temp/lead': file:projects/lead.tgz
'@rush-temp/lead-assets': file:projects/lead-assets.tgz
'@rush-temp/lead-resources': file:projects/lead-resources.tgz_096c09b0b673a57c275d9767a12070b1
@ -232,6 +239,7 @@ dependencies:
'@rush-temp/model-core': file:projects/model-core.tgz_typescript@4.5.4
'@rush-temp/model-demo': file:projects/model-demo.tgz_typescript@4.5.4
'@rush-temp/model-gmail': file:projects/model-gmail.tgz_typescript@4.5.4
'@rush-temp/model-inventory': file:projects/model-inventory.tgz_typescript@4.5.4
'@rush-temp/model-lead': file:projects/model-lead.tgz_typescript@4.5.4
'@rush-temp/model-recruit': file:projects/model-recruit.tgz_typescript@4.5.4
'@rush-temp/model-rig': file:projects/model-rig.tgz_37f79b97d0d86442e45d380c86f520c5
@ -11436,6 +11444,82 @@ packages:
- supports-color
dev: false
file:projects/inventory-assets.tgz:
resolution: {integrity: sha512-z58zb5383o9j4S1y3tBAYdcGXdI9IORHbh5w+z/lI2PnS41B+HK1JsIrlrep+XG/w+xyAWn3OHlw77VJMQEDNw==, tarball: file:projects/inventory-assets.tgz}
name: '@rush-temp/inventory-assets'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.8
'@types/heft-jest': 1.0.2
'@types/node': 16.11.14
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.5.1
typescript: 4.5.4
transitivePeerDependencies:
- supports-color
dev: false
file:projects/inventory-resources.tgz_096c09b0b673a57c275d9767a12070b1:
resolution: {integrity: sha512-PiKSp7YMOeYnOnyDukPG1Qno6QmImWvc3qBJUY5ZKXiQyZWA9JpWqJm3WfoaprfoLVKCiSvqkA2r2XrqfIjJcQ==, tarball: file:projects/inventory-resources.tgz}
id: file:projects/inventory-resources.tgz
name: '@rush-temp/inventory-resources'
version: 0.0.0
dependencies:
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
eslint-plugin-svelte3: 3.2.1_eslint@7.32.0+svelte@3.44.3
prettier: 2.5.1
prettier-plugin-svelte: 2.5.1_prettier@2.5.1+svelte@3.44.3
sass: 1.45.0
svelte: 3.44.3
svelte-check: 2.2.11_4374c622c67ed7479ff0e44c29d09bce
svelte-loader: 3.1.2_svelte@3.44.3
svelte-preprocess: 4.10.1_14d64cad431e31f100de7363af24a44f
typescript: 4.5.4
transitivePeerDependencies:
- '@babel/core'
- coffeescript
- less
- node-sass
- postcss
- postcss-load-config
- pug
- stylus
- sugarss
- supports-color
dev: false
file:projects/inventory.tgz:
resolution: {integrity: sha512-gd3ZCKg7WVdifG6BWuXJhwBNgjdyaFyTaWdlH9e+gFI2a9M9HYeIVmYBWDC2X6QP6//G2yv6sykXytwVaMgqKw==, tarball: file:projects/inventory.tgz}
name: '@rush-temp/inventory'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.8
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.5.1
typescript: 4.5.4
transitivePeerDependencies:
- supports-color
dev: false
file:projects/lead-assets.tgz:
resolution: {integrity: sha512-cRYB8PutP6HmaJjoEMLIEyMQEhKAQaCu0w2NJMF5TUW9vokia/22TXsHo1+xEGI1rx2epywbGXet/fL40tdbDw==, tarball: file:projects/lead-assets.tgz}
name: '@rush-temp/lead-assets'
@ -11580,7 +11664,7 @@ packages:
dev: false
file:projects/model-all.tgz_typescript@4.5.4:
resolution: {integrity: sha512-oKPQz2zoE53ROTtb76Vp43+PatfI+CKrdq0sUleUVaKPfWw74BKTXmqSAkZU1PVnr/DaKY/Gnl9Eo9JrXOUiig==, tarball: file:projects/model-all.tgz}
resolution: {integrity: sha512-QqT4D4AgEQSvRi5epmizX6ipSCnOlaklFy5ne2rUIVJ1MU0cLgOfIXdCjkl47VTsT98JLOWSjv7y5X82bRGnHw==, tarball: file:projects/model-all.tgz}
id: file:projects/model-all.tgz
name: '@rush-temp/model-all'
version: 0.0.0
@ -11730,6 +11814,27 @@ packages:
- typescript
dev: false
file:projects/model-inventory.tgz_typescript@4.5.4:
resolution: {integrity: sha512-P+V2OydmT65gpPhJm85tPkIn5uLRKQmu2sMHg/1oRJLXKCeQz1wAgb5ZzlzmgbilrrfchcwYtJHyPANNcnBrdg==, tarball: file:projects/model-inventory.tgz}
id: file:projects/model-inventory.tgz
name: '@rush-temp/model-inventory'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.8
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.7.0_c25e8c1f4f4f7aaed27aa6f9ce042237
'@typescript-eslint/parser': 5.7.0_eslint@7.32.0+typescript@4.5.4
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_ce2fa0c4dfa1c256100cababd749a13a
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.5.1
transitivePeerDependencies:
- supports-color
- typescript
dev: false
file:projects/model-lead.tgz_typescript@4.5.4:
resolution: {integrity: sha512-3+Wf+/TdMpbuSoiTaI/ga+1KHSsPd1q9MDtpG6i4oAhxt/48aTZnfOV4nt9hrpWy2MmQWVhK9YJFTaKGIgKwOA==, tarball: file:projects/model-lead.tgz}
id: file:projects/model-lead.tgz
@ -12154,7 +12259,7 @@ packages:
dev: false
file:projects/prod.tgz_sass@1.45.0+typescript@4.5.4:
resolution: {integrity: sha512-7OeW4OKQlYw/FrF1rSmbwacpTXSVzNnIPCb/mIP/Q4Kc+uxroItvCRRYUqVH/V9u8W7nyTE7CjDM+KrADL/BAg==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-aYcYW/uvTMNxOOsjG9io+pTV+pyk0a2CIsoJsF8tFnywgIBJb2X39Yj33nmjRvPpYdsaF4ujn0IdbK4IfM0kZw==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0

View File

@ -90,6 +90,9 @@
"@anticrm/gmail-assets": "~0.6.0",
"@anticrm/gmail-resources": "~0.6.0",
"@anticrm/image-cropper": "~0.6.0",
"@anticrm/image-cropper-resources": "~0.6.0"
"@anticrm/image-cropper-resources": "~0.6.0",
"@anticrm/inventory": "~0.6.0",
"@anticrm/inventory-assets": "~0.6.0",
"@anticrm/inventory-resources": "~0.6.0"
}
}

View File

@ -30,6 +30,7 @@ import { leadId } from '@anticrm/lead'
import { clientId } from '@anticrm/client'
import { gmailId } from '@anticrm/gmail'
import { imageCropperId } from '@anticrm/image-cropper'
import { inventoryId } from '@anticrm/inventory'
import '@anticrm/login-assets'
import '@anticrm/task-assets'
@ -44,6 +45,7 @@ import '@anticrm/telegram-assets'
import '@anticrm/lead-assets'
import '@anticrm/gmail-assets'
import '@anticrm/workbench-assets'
import '@anticrm/inventory-assets'
import { setMetadata } from '@anticrm/platform'
export async function configurePlatform() {
@ -73,4 +75,5 @@ export async function configurePlatform() {
addLocation(attachmentId, () => import(/* webpackChunkName: "attachment" */ '@anticrm/attachment-resources'))
addLocation(gmailId, () => import(/* webpackChunkName: "gmail" */ '@anticrm/gmail-resources'))
addLocation(imageCropperId, () => import(/* webpackChunkName: "image-cropper" */ '@anticrm/image-cropper-resources'))
addLocation(inventoryId, () => import(/* webpackChunkName: "inventory" */ '@anticrm/inventory-resources'))
}

View File

@ -46,6 +46,7 @@
"@anticrm/model-activity": "~0.6.0",
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/model-gmail": "~0.6.0",
"@anticrm/model-inventory": "~0.6.0",
"@anticrm/core": "~0.6.13"
}
}

View File

@ -27,6 +27,7 @@ import { createModel as telegramModel } from '@anticrm/model-telegram'
import { createModel as attachmentModel } from '@anticrm/model-attachment'
import { createModel as leadModel } from '@anticrm/model-lead'
import { createModel as gmailModel } from '@anticrm/model-gmail'
import { createModel as inventoryModel } from '@anticrm/model-inventory'
import { createModel as serverCoreModel } from '@anticrm/model-server-core'
import { createModel as serverChunterModel } from '@anticrm/model-server-chunter'
@ -50,6 +51,7 @@ settingModel(builder)
telegramModel(builder)
leadModel(builder)
gmailModel(builder)
inventoryModel(builder)
serverCoreModel(builder)
serverChunterModel(builder)

View File

@ -17,7 +17,7 @@ import type { IntlString } from '@anticrm/platform'
import { Builder, Model, Prop, UX, TypeString, TypeTimestamp } from '@anticrm/model'
import type { Domain } from '@anticrm/core'
import core, { TAttachedDoc } from '@anticrm/model-core'
import type { Attachment } from '@anticrm/attachment'
import type { Attachment, Photo } from '@anticrm/attachment'
import activity from '@anticrm/activity'
import view from '@anticrm/model-view'
@ -46,8 +46,12 @@ export class TAttachment extends TAttachedDoc implements Attachment {
lastModified!: number
}
@Model(attachment.class.Photo, attachment.class.Attachment)
@UX('Photo' as IntlString)
export class TPhoto extends TAttachment implements Photo {}
export function createModel (builder: Builder): void {
builder.createModel(TAttachment)
builder.createModel(TAttachment, TPhoto)
builder.mixin(attachment.class.Attachment, core.class.Class, view.mixin.AttributePresenter, {
presenter: attachment.component.AttachmentPresenter
@ -57,6 +61,10 @@ export function createModel (builder: Builder): void {
editor: attachment.component.Attachments
})
builder.mixin(attachment.class.Photo, core.class.Class, view.mixin.AttributeEditor, {
editor: attachment.component.Photos
})
builder.createDoc(
activity.class.TxViewlet,
core.space.Model,

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/model-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/model-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

View File

@ -0,0 +1,40 @@
{
"name": "@anticrm/model-inventory",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/model-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1"
},
"dependencies": {
"@anticrm/core": "~0.6.11",
"@anticrm/model": "~0.6.0",
"@anticrm/model-workbench": "~0.6.0",
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/model-core": "~0.6.0",
"@anticrm/ui": "~0.6.0",
"@anticrm/platform": "~0.6.5",
"@anticrm/inventory": "~0.6.0",
"@anticrm/inventory-resources": "~0.6.0",
"@anticrm/view": "~0.6.0",
"@anticrm/workbench": "~0.6.1"
}
}

View File

@ -0,0 +1,148 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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.
//
// To help typescript locate view plugin properly
import type { Category, Product, Variant } from '@anticrm/inventory'
import { Doc, Domain, FindOptions, Ref } from '@anticrm/core'
import { Builder, Collection, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
import core, { TAttachedDoc } from '@anticrm/model-core'
import type { IntlString } from '@anticrm/platform'
import type {} from '@anticrm/view'
import inventory from './plugin'
import workbench from '@anticrm/model-workbench'
import view from '@anticrm/view'
import attachment from '@anticrm/model-attachment'
export const DOMAIN_INVENTORY = 'inventory' as Domain
@Model(inventory.class.Category, core.class.AttachedDoc, DOMAIN_INVENTORY)
@UX(inventory.string.Category, inventory.icon.Categories, undefined, 'name')
export class TCategory extends TAttachedDoc implements Category {
@Prop(TypeString(), 'Name' as IntlString)
name!: string
}
@Model(inventory.class.Product, core.class.AttachedDoc, DOMAIN_INVENTORY)
@UX(inventory.string.Product, inventory.icon.Products, undefined, 'name')
export class TProduct extends TAttachedDoc implements Product {
// We need to declare, to provide property with label
@Prop(TypeRef(inventory.class.Category), inventory.string.Category)
declare attachedTo: Ref<Category>
@Prop(TypeString(), 'Name' as IntlString)
name!: string
@Prop(Collection(attachment.class.Photo), attachment.string.Photos)
photos?: number
@Prop(Collection(inventory.class.Variant), inventory.string.Variants)
variants?: number
@Prop(Collection(attachment.class.Attachment), 'Attachments' as IntlString)
attachments?: number
}
@Model(inventory.class.Variant, core.class.AttachedDoc, DOMAIN_INVENTORY)
@UX(inventory.string.Variant, inventory.icon.Variant, undefined, 'name')
export class TVariant extends TAttachedDoc implements Variant {
// We need to declare, to provide property with label
@Prop(TypeRef(inventory.class.Product), inventory.string.Product)
declare attachedTo: Ref<Product>
@Prop(TypeString(), 'Name' as IntlString)
name!: string
@Prop(TypeString(), inventory.string.SKU)
sku!: string
}
export function createModel (builder: Builder): void {
builder.createModel(TCategory, TProduct, TVariant)
builder.mixin(inventory.class.Category, core.class.Class, view.mixin.AttributePresenter, {
presenter: inventory.component.CategoryPresenter
})
builder.mixin(inventory.class.Product, core.class.Class, view.mixin.AttributePresenter, {
presenter: inventory.component.ProductPresenter
})
builder.mixin(inventory.class.Variant, core.class.Class, view.mixin.AttributePresenter, {
presenter: inventory.component.VariantPresenter
})
builder.mixin(inventory.class.Variant, core.class.Class, view.mixin.AttributeEditor, {
editor: inventory.component.Variants
})
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: inventory.class.Product,
descriptor: view.viewlet.Table,
open: inventory.component.EditProduct,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
options: {
lookup: {
attachedTo: inventory.class.Category
}
} as FindOptions<Doc>,
config: ['', '$lookup.attachedTo', 'modifiedOn']
})
builder.createDoc(
workbench.class.Application,
core.space.Model,
{
label: inventory.string.Inventory,
icon: inventory.icon.InventoryApplication,
hidden: false,
navigatorModel: {
specials: [
{
id: 'Categories',
label: inventory.string.Categories,
icon: inventory.icon.Categories,
component: inventory.component.Categories
},
{
id: 'Products',
label: inventory.string.Products,
icon: inventory.icon.Products,
component: inventory.component.Products
}
],
spaces: []
}
},
inventory.app.Inventory
)
builder.createDoc(view.class.ActionTarget, core.space.Model, {
target: inventory.class.Category,
action: inventory.action.CreateSubcategory
})
builder.createDoc(
view.class.Action,
core.space.Model,
{
label: inventory.string.CreateSubcategory,
icon: inventory.icon.Categories,
action: inventory.actionImpl.CreateSubcategory
},
inventory.action.CreateSubcategory
)
}
export { default } from './plugin'

View File

@ -0,0 +1,45 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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.
//
import type { Doc, Ref } from '@anticrm/core'
import { inventoryId } from '@anticrm/inventory'
import inventory from '@anticrm/inventory-resources/src/plugin'
import { mergeIds, Resource } from '@anticrm/platform'
import type { AnyComponent } from '@anticrm/ui'
import { Action } from '@anticrm/view'
import { Application } from '@anticrm/workbench'
export default mergeIds(inventoryId, inventory, {
app: {
Inventory: '' as Ref<Application>
},
action: {
CreateSubcategory: '' as Ref<Action>
},
actionImpl: {
CreateSubcategory: '' as Resource<(object: Doc) => Promise<void>>
},
component: {
Categories: '' as AnyComponent,
Products: '' as AnyComponent,
CreateProduct: '' as AnyComponent,
EditProduct: '' as AnyComponent,
CategoryPresenter: '' as AnyComponent,
Variants: '' as AnyComponent,
ProductPresenter: '' as AnyComponent,
VariantPresenter: '' as AnyComponent
}
})

View File

@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
}
}

View File

@ -13,7 +13,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import activity from '@anticrm/activity'
import type { Doc } from '@anticrm/core'
@ -23,6 +22,7 @@
import { createEventDispatcher } from 'svelte'
export let title: string
export let subtitle: string | undefined = undefined
export let icon: Asset | AnySvelteComponent
export let fullSize: boolean = true
export let rightSection: AnyComponent | undefined = undefined
@ -31,50 +31,70 @@
const dispatch = createEventDispatcher()
</script>
<div class="overlay" on:click={() => { dispatch('close') }}/>
<div
class="overlay"
on:click={() => {
dispatch('close')
}}
/>
<div class="dialog-container" class:fullSize>
{#if fullSize}
<div class="leftSection">
<div class="flex-between header">
<Icon {icon} size={'large'} />
<div class="flex-grow ml-4 flex-col">
<div class="fs-title">{title}</div>
<div class="small-text content-dark-color">Candidate pool name</div>
{#if fullSize}
<div class="leftSection">
<div class="flex-between header">
<Icon {icon} size={'large'} />
<div class="flex-grow ml-4 flex-col">
<div class="fs-title">{title}</div>
{#if subtitle}
<div class="small-text content-dark-color">{subtitle}</div>
{/if}
</div>
<ActionIcon icon={IconMoreH} size={'medium'} />
</div>
{#if $$slots.subtitle}<div class="flex-row-center subtitle"><slot name="subtitle" /></div>{/if}
<div class="flex-col scroll-container">
<div class="flex-col content">
<slot />
</div>
</div>
<ActionIcon icon={IconMoreH} size={'medium'} />
</div>
{#if $$slots.subtitle}<div class="flex-row-center subtitle"><slot name="subtitle" /></div>{/if}
<div class="flex-col scroll-container">
<div class="flex-col content">
<div class="rightSection">
<Component is={rightSection ?? activity.component.Activity} props={{ object, fullSize }} />
</div>
{:else}
<div class="unionSection">
<div class="flex-row-center header">
<Icon {icon} size={'large'} />
<div class="flex-grow ml-4 flex-col">
<div class="fs-title">{title}</div>
<div class="small-text content-dark-color">Candidate pool name</div>
</div>
<ActionIcon icon={IconMoreH} size={'medium'} />
</div>
{#if $$slots.subtitle}<div class="flex-row-center subtitle"><slot name="subtitle" /></div>{/if}
<Component is={activity.component.Activity} props={{ object, fullSize }}>
<slot />
</div>
</Component>
</div>
</div>
<div class="rightSection">
<Component is={rightSection ?? activity.component.Activity} props={{object, fullSize}}/>
</div>
{:else}
<div class="unionSection">
<div class="flex-row-center header">
<Icon {icon} size={'large'} />
<div class="flex-grow ml-4 flex-col">
<div class="fs-title">{title}</div>
<div class="small-text content-dark-color">Candidate pool name</div>
</div>
<ActionIcon icon={IconMoreH} size={'medium'} />
</div>
{#if $$slots.subtitle}<div class="flex-row-center subtitle"><slot name="subtitle" /></div>{/if}
<Component is={activity.component.Activity} props={{object, fullSize}}>
<slot />
</Component>
</div>
{/if}
{/if}
<div class="tools">
<div class="tool" on:click={() => { fullSize = !fullSize }}><div class="icon"><IconExpand size={'small'} /></div></div>
<div class="tool" on:click={() => { dispatch('close') }}><div class="icon"><IconClose size={'small'} /></div></div>
<div
class="tool"
on:click={() => {
fullSize = !fullSize
}}
>
<div class="icon"><IconExpand size={'small'} /></div>
</div>
<div
class="tool"
on:click={() => {
dispatch('close')
}}
>
<div class="icon"><IconClose size={'small'} /></div>
</div>
</div>
</div>
@ -116,9 +136,11 @@
display: flex;
flex-direction: column;
height: max-content;
.header { padding: 0 6rem 0 2.5rem; }
height: max-content;
.header {
padding: 0 6rem 0 2.5rem;
}
}
.fullSize {
@ -126,7 +148,8 @@
left: 1rem;
}
.leftSection, .rightSection {
.leftSection,
.rightSection {
flex-basis: 50%;
width: 50%;
min-height: 0;
@ -140,7 +163,7 @@
margin: 2.5rem 2rem 1.5rem;
.content {
flex-shrink: 0;
margin: .5rem .5rem 0;
margin: 0.5rem 0.5rem 0;
}
}
}
@ -158,7 +181,9 @@
margin-left: 1rem;
color: var(--theme-content-accent-color);
cursor: pointer;
&:hover { color: var(--theme-caption-color); }
&:hover {
color: var(--theme-caption-color);
}
}
}
@ -171,4 +196,4 @@
background: #000;
opacity: .5;
}
</style>
</style>

View File

@ -3,6 +3,7 @@
"UploadDropFilesHere": "Upload or drop files here",
"NoAttachments": "There are no attachments for this",
"AddAttachment": "uploaded an attachment",
"Attachments": "Attachments"
"Attachments": "Attachments",
"Photos": "Photos"
}
}

View File

@ -0,0 +1,195 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { Photo } from '@anticrm/attachment'
import { Class, Doc, Ref, Space } from '@anticrm/core'
import { setPlatformStatus, unknownError } from '@anticrm/platform'
import { createQuery, getClient, getFileUrl, PDFViewer } from '@anticrm/presentation'
import { CircleButton, IconAdd, Label, showPopup, Spinner } from '@anticrm/ui'
import attachment from '../plugin'
import { uploadFile } from '../utils'
import UploadDuo from './icons/UploadDuo.svelte'
export let objectId: Ref<Doc>
export let space: Ref<Space>
export let _class: Ref<Class<Doc>>
let inputFile: HTMLInputElement
let loading = 0
let images: Photo[] = []
const client = getClient()
const query = createQuery()
query.query(
attachment.class.Photo,
{
attachedTo: objectId
},
(res) => {
images = res
}
)
async function create (file: File) {
if (!file.type.startsWith('image/')) return
loading++
try {
const uuid = await uploadFile(file, space, objectId)
console.log('uploaded file uuid', uuid)
client.addCollection(attachment.class.Photo, space, objectId, _class, 'photos', {
name: file.name,
file: uuid,
type: file.type,
size: file.size,
lastModified: file.lastModified
})
} catch (err: any) {
setPlatformStatus(unknownError(err))
} finally {
loading--
}
}
function fileSelected () {
const list = inputFile.files
if (list === null || list.length === 0) return
for (let index = 0; index < list.length; index++) {
const file = list.item(index)
if (file !== null) create(file)
}
}
function fileDrop (e: DragEvent) {
const list = e.dataTransfer?.files
if (list === undefined || list.length === 0) return
for (let index = 0; index < list.length; index++) {
const file = list.item(index)
if (file !== null) create(file)
}
}
let dragover = false
function click (ev: Event, item?: Photo): void {
const el: HTMLElement = ev.currentTarget as HTMLElement
el.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' })
if (item !== undefined) {
showPopup(PDFViewer, { file: item.file, name: item.name }, 'right')
} else {
inputFile.click()
}
}
</script>
<div class="attachments-container">
<div class="flex-row-center">
<span class="title"><Label label={attachment.string.Photos} /></span>
{#if loading}
<Spinner />
{:else}
<CircleButton
icon={IconAdd}
size={'small'}
selected
on:click={() => {
inputFile.click()
}}
/>
{/if}
<input
bind:this={inputFile}
multiple
type="file"
name="file"
accept="image/*"
id="file"
style="display: none"
on:change={fileSelected}
/>
</div>
<div
class="flex-row-center mt-5 zone-container"
class:solid={dragover}
on:dragover|preventDefault={() => {
dragover = true
}}
on:dragleave={() => {
dragover = false
}}
on:drop|preventDefault|stopPropagation={fileDrop}
>
{#each images as image (image._id)}
<div
class="item"
on:click={(ev) => {
click(ev, image)
}}
>
<img src={getFileUrl(image.file)} alt={image.name} />
</div>
{/each}
<div class="flex-center item new-item" on:click={click}>
<UploadDuo size={'large'} />
</div>
</div>
</div>
<style lang="scss">
.attachments-container {
display: flex;
flex-direction: column;
.title {
margin-right: 0.75rem;
font-weight: 500;
font-size: 1.25rem;
color: var(--theme-caption-color);
}
}
.zone-container {
padding: 1rem;
color: var(--theme-caption-color);
background: var(--theme-bg-accent-color);
border: 1px solid var(--theme-bg-focused-color);
border-radius: 0.75rem;
overflow-x: auto;
.item {
width: 5rem;
min-width: 5rem;
height: 5rem;
border-radius: 0.5rem;
overflow: hidden;
cursor: pointer;
img {
width: 5rem;
height: 5rem;
}
}
.item + .item {
margin-left: 0.625rem;
}
.new-item {
background: var(--theme-bg-accent-color);
border: 1px dashed var(--theme-zone-border-lite);
}
}
</style>

View File

@ -1,14 +1,14 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
//
// 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.
//
@ -17,15 +17,18 @@ import AttachmentsPresenter from './components/AttachmentsPresenter.svelte'
import AttachmentPresenter from './components/AttachmentPresenter.svelte'
import TxAttachmentCreate from './components/activity/TxAttachmentCreate.svelte'
import Attachments from './components/Attachments.svelte'
import Photos from './components/Photos.svelte'
import { Resources } from '@anticrm/platform'
import { uploadFile, deleteFile } from './utils'
export { Attachments, AttachmentsPresenter }
export default async () => ({
export default async (): Promise<Resources> => ({
component: {
AttachmentsPresenter,
AttachmentPresenter,
Attachments
Attachments,
Photos
},
activity: {
TxAttachmentCreate

View File

@ -1,14 +1,15 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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.
//
@ -22,6 +23,7 @@ export default mergeIds(attachmentId, attachment, {
string: {
NoAttachments: '' as IntlString,
UploadDropFilesHere: '' as IntlString,
Attachments: '' as IntlString
Attachments: '' as IntlString,
Photos: '' as IntlString
}
})

View File

@ -30,6 +30,11 @@ export interface Attachment extends AttachedDoc {
lastModified: number
}
/**
* @public
*/
export interface Photo extends Attachment {}
/**
* @public
*/
@ -37,13 +42,15 @@ export const attachmentId = 'attachment' as Plugin
export default plugin(attachmentId, {
component: {
Attachments: '' as AnyComponent
Attachments: '' as AnyComponent,
Photos: '' as AnyComponent
},
icon: {
Attachment: '' as Asset
},
class: {
Attachment: '' as Ref<Class<Attachment>>
Attachment: '' as Ref<Class<Attachment>>,
Photo: '' as Ref<Class<Photo>>
},
helper: {
UploadFile: '' as Resource<(file: File, space?: Ref<Space>, attachedTo?: Ref<Doc>) => Promise<string>>,

View File

@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="inventory" viewBox="0 0 24 24" fill="none">
<path d="M13.1716 3H9C7.11438 3 6.17157 3 5.58579 3.58579C5 4.17157 5 5.11438 5 7V17C5 18.8856 5 19.8284 5.58579 20.4142C6.17157 21 7.11438 21 9 21H15C16.8856 21 17.8284 21 18.4142 20.4142C19 19.8284 19 18.8856 19 17V8.82843C19 8.41968 19 8.2153 18.9239 8.03153C18.8478 7.84776 18.7032 7.70324 18.4142 7.41421L14.5858 3.58579C14.2968 3.29676 14.1522 3.15224 13.9685 3.07612C13.7847 3 13.5803 3 13.1716 3Z" stroke="currentColor" stroke-width="1.3"/>
<path d="M9 13L15 13" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
<path d="M9 17L13 17" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
<path d="M13 3V7C13 7.94281 13 8.41421 13.2929 8.70711C13.5858 9 14.0572 9 15 9H19" stroke="currentColor" stroke-width="1.3"/></symbol>
<symbol id="categories" viewBox="0 0 16 16" fill="none">
<path d="M2 6.66669C2 4.78107 2 3.83826 2.58579 3.25247C3.17157 2.66669 4.11438 2.66669 6 2.66669H10C11.8856 2.66669 12.8284 2.66669 13.4142 3.25247C14 3.83826 14 4.78107 14 6.66669V9.33335C14 11.219 14 12.1618 13.4142 12.7476C12.8284 13.3334 11.8856 13.3334 10 13.3334H6C4.11438 13.3334 3.17157 13.3334 2.58579 12.7476C2 12.1618 2 11.219 2 9.33335V6.66669Z" stroke="white"/>
<path d="M5.33398 2.66669V2.66669C5.42787 2.66669 5.47482 2.66669 5.51834 2.66858C6.21284 2.69886 6.8418 3.08757 7.17946 3.69521C7.20062 3.73329 7.22161 3.77528 7.2636 3.85926L7.75628 4.8446C8.10324 5.53853 8.27672 5.88549 8.54248 6.13227C8.73342 6.30957 8.95717 6.44786 9.20115 6.53935C9.54072 6.66669 9.92864 6.66669 10.7045 6.66669V6.66669C11.9155 6.66669 12.5209 6.66669 12.9753 6.92023C13.2991 7.10097 13.5664 7.3682 13.7471 7.69206C14.0007 8.1464 14.0007 8.75189 14.0007 9.96287V10.6667" stroke="white"/>
</symbol>
<symbol id="products" viewBox="0 0 16 16" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.54693 13.1966L2.80604 9.45673C2.6101 9.26106 2.5 8.99554 2.5 8.71866C2.5 8.44178 2.6101 8.17626 2.80604 7.98059L8.28258 2.5L13.5 2.5L13.5 7.71605L8.01824 13.1966C7.61124 13.6011 6.95392 13.6011 6.54693 13.1966Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="variant" viewBox="0 0 16 16" fill="none">
<rect x="12" y="6" width="2.66667" height="2.66667" rx="1.33333" transform="rotate(90 12 6)" stroke="currentColor"/>
<rect x="12" y="11.3333" width="2.66667" height="2.66667" rx="1.33333" transform="rotate(90 12 11.3333)" stroke="currentColor"/>
<rect x="2" y="4.66675" width="2.66667" height="2.66667" rx="1.33333" transform="rotate(-90 2 4.66675)" stroke="currentColor"/>
<path d="M3.3335 5.33325V8.66658C3.3335 10.5522 3.3335 11.495 3.91928 12.0808C4.50507 12.6666 5.44788 12.6666 7.3335 12.6666H9.3335" stroke="currentColor"/>
<path d="M3.3335 4.66675V4.66675C3.3335 5.28673 3.3335 5.59672 3.40164 5.85105C3.58658 6.54124 4.12567 7.08033 4.81586 7.26527C5.07019 7.33342 5.38018 7.33342 6.00016 7.33342H9.3335" stroke="currentColor"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,23 @@
{
"status": {
"CategoryRequired": "Category required",
"NameRequired": "Name required"
},
"string": {
"Categories": "Categories",
"Category": "Category",
"CreateCategoryShort": "+ Category",
"CreateCategory": "Create category",
"CreateSubcategory": "Create subcategory",
"Inventory": "Inventory",
"CreateProductShort": "+ Product",
"CreateProduct": "Create product",
"Products": "Products",
"Product": "Product",
"Variant": "Variant",
"SKU": "SKU",
"Variants": "Variants",
"NoVariantsForProduct": "There are no variants for this product.",
"CreateVariant": "Create variant"
}
}

View File

@ -0,0 +1,33 @@
{
"name": "@anticrm/inventory-assets",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"lint": "eslint src",
"lint:fix": "eslint --fix src",
"format": "prettier --write src && eslint --fix src",
"build:watch": "tsc"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint": "^7.32.0",
"prettier": "^2.4.1",
"@types/heft-jest": "^1.0.2",
"@rushstack/heft": "^0.41.1",
"typescript": "^4.3.5",
"@types/node": "^16.4.10"
},
"dependencies": {
"@anticrm/platform": "~0.6.5",
"@anticrm/inventory": "~0.6.0"
}
}

View File

@ -0,0 +1,27 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { addStringsLoader, loadMetadata } from '@anticrm/platform'
import inventory, { inventoryId } from '@anticrm/inventory'
const icons = require('../assets/icons.svg')
loadMetadata(inventory.icon, {
InventoryApplication: `${icons}#inventory`,
Categories: `${icons}#categories`,
Variant: `${icons}#variant`,
Products: `${icons}#products`
})
addStringsLoader(inventoryId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/ui/config/eslint.config.json'],
parserOptions: { tsconfigRootDir: __dirname },
settings: {
'svelte3/ignore-styles': () => true
}
}

View File

@ -0,0 +1,43 @@
{
"name": "@anticrm/inventory-resources",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "echo 'no build for ui'",
"build:docs": "api-extractor run --local",
"lint": "svelte-check && eslint",
"lint:fix": "eslint --fix src",
"format": "prettier --write --plugin-search-dir=. src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"svelte-loader": "^3.1.2",
"sass": "^1.37.5",
"svelte-preprocess": "^4.7.4",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-svelte3": "~3.2.1",
"prettier-plugin-svelte": "^2.2.0",
"prettier": "^2.4.1",
"svelte-check": "^2.2.10",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/platform": "~0.6.5",
"svelte": "^3.37.0",
"@anticrm/inventory": "~0.6.0",
"@anticrm/ui": "~0.6.0",
"@anticrm/panel": "~0.6.0",
"@anticrm/presentation": "~0.6.2",
"@anticrm/view": "~0.6.0",
"@anticrm/view-resources": "~0.6.0",
"@anticrm/core": "~0.6.11"
}
}

View File

@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}

View File

@ -0,0 +1,114 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { Button, EditWithIcon, Icon, IconSearch, Label, ScrollBox, showPopup } from '@anticrm/ui'
import HierarchyView from './HierarchyView.svelte'
import CreateCategory from './CreateCategory.svelte'
import inventory from '../plugin'
let search = ''
$: resultQuery = search === '' ? {} : { $search: search }
function showCreateDialog (ev: Event) {
showPopup(CreateCategory, { space: inventory.space.Category }, ev.target as HTMLElement)
}
</script>
<div class="categories-header-container">
<div class="header-container">
<div class="flex-row-center">
<span class="icon"><Icon icon={inventory.icon.Categories} size={'small'} /></span>
<span class="label"><Label label={inventory.string.Categories} /></span>
</div>
</div>
<EditWithIcon
icon={IconSearch}
placeholder={'Search'}
bind:value={search}
on:change={() => {
resultQuery = {}
}}
/>
<Button
label={inventory.string.CreateCategoryShort}
primary={true}
size={'small'}
on:click={(ev) => showCreateDialog(ev)}
/>
</div>
<div class="container">
<div class="panel-component">
<ScrollBox vertical stretch noShift>
<HierarchyView _class={inventory.class.Category} config={['', 'modifiedOn']} options={{}} query={resultQuery} />
</ScrollBox>
</div>
</div>
<style lang="scss">
.container {
display: flex;
height: 100%;
padding-bottom: 1.25rem;
.panel-component {
flex-grow: 1;
display: flex;
flex-direction: column;
margin-right: 1rem;
height: 100%;
border-radius: 1.25rem;
background-color: var(--theme-bg-color);
overflow: hidden;
}
}
.categories-header-container {
display: grid;
grid-template-columns: auto;
grid-auto-flow: column;
grid-auto-columns: min-content;
gap: 0.75rem;
align-items: center;
padding: 0 1.75rem 0 2.5rem;
height: 4rem;
min-height: 4rem;
white-space: nowrap;
.header-container {
display: flex;
flex-direction: column;
flex-grow: 1;
.icon {
margin-right: 0.5rem;
opacity: 0.6;
}
.label {
flex-grow: 1;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 35rem;
}
.label {
font-weight: 500;
font-size: 1rem;
color: var(--theme-caption-color);
}
}
}
</style>

View File

@ -0,0 +1,26 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { Category } from '@anticrm/inventory'
export let value: Category
</script>
{#if value}
<div class="overflow-label sm-tool-icon">
{value.name}
</div>
{/if}

View File

@ -0,0 +1,71 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { AttachedData, Doc, Ref } from '@anticrm/core'
import { generateId } from '@anticrm/core'
import { OK, Status } from '@anticrm/platform'
import { Card, getClient } from '@anticrm/presentation'
import type { Category } from '@anticrm/inventory'
import { EditBox, Grid, Status as StatusControl } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import inventory from '../plugin'
const status: Status = OK
export let attachedTo: Ref<Doc> = inventory.global.Category
let name: string = ''
const dispatch = createEventDispatcher()
const client = getClient()
const inventoryId = generateId() as Ref<Category>
export function canClose (): boolean {
return name !== ''
}
async function create () {
const value: AttachedData<Category> = {
name
}
await client.addCollection(
inventory.class.Category,
inventory.space.Category,
attachedTo,
inventory.class.Category,
'categories',
value,
inventoryId
)
dispatch('close')
}
</script>
<Card
label={inventory.string.CreateCategory}
okAction={create}
space={inventory.space.Category}
canSave={name.length > 0}
on:close={() => {
dispatch('close')
}}
>
<StatusControl slot="error" {status} />
<Grid column={1} rowGap={1.5}>
<EditBox label={inventory.string.Category} bind:value={name} placeholder="Category" maxWidth={'16rem'} focus />
</Grid>
</Card>

View File

@ -0,0 +1,90 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { Account, generateId, Ref } from '@anticrm/core'
import { Card, createQuery, getClient } from '@anticrm/presentation'
import { DropdownLabels, EditBox, Grid } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import inventory from '../plugin'
import { Category, Product } from '@anticrm/inventory'
import { DropdownTextItem } from '@anticrm/ui/src/types'
const doc: Product = {
name: '',
attachedTo: undefined,
attachedToClass: inventory.class.Category,
_class: inventory.class.Product,
space: inventory.space.Products,
_id: generateId(),
collection: 'products',
modifiedOn: Date.now(),
modifiedBy: '' as Ref<Account>
}
const dispatch = createEventDispatcher()
const client = getClient()
export function canClose (): boolean {
return doc.attachedTo.length === 0 && doc.name.length === 0
}
async function create () {
const categoryInstance = await client.findOne(inventory.class.Category, { _id: doc.attachedTo as Ref<Category> })
if (categoryInstance === undefined) {
throw new Error('category not found')
}
await client.addCollection(
inventory.class.Product,
doc.space,
doc.attachedTo,
categoryInstance._class,
'products',
{
name: doc.name
}
)
}
let categories: DropdownTextItem[] = []
const categoriesQ = createQuery()
$: categoriesQ.query(inventory.class.Category, {}, (result) => {
categories = result.map((c) => {
return { id: c._id, label: c.name }
})
})
</script>
<Card
label={inventory.string.CreateProduct}
okAction={create}
canSave={doc.name.trim().length > 0 && doc.attachedTo !== undefined}
bind:space={doc.space}
on:close={() => {
dispatch('close')
}}
>
<Grid column={1} rowGap={1.75}>
<EditBox label={inventory.string.Product} bind:value={doc.name} placeholder="Product" maxWidth={'16rem'} focus />
<DropdownLabels
header
items={categories}
bind:selected={doc.attachedTo}
caption={inventory.string.Categories}
title={inventory.string.Category}
/>
</Grid>
</Card>

View File

@ -0,0 +1,72 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { Account, generateId, Ref } from '@anticrm/core'
import { Card, getClient } from '@anticrm/presentation'
import { EditBox, Grid } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import inventory from '../plugin'
import { Product, Variant } from '@anticrm/inventory'
export let product: Ref<Product>
const doc: Variant = {
name: '',
sku: '',
attachedTo: product,
attachedToClass: inventory.class.Product,
_class: inventory.class.Variant,
space: inventory.space.Products,
_id: generateId(),
collection: 'variants',
modifiedOn: Date.now(),
modifiedBy: '' as Ref<Account>
}
const dispatch = createEventDispatcher()
const client = getClient()
export function canClose (): boolean {
return doc.attachedTo.length === 0 && doc.name.length === 0
}
async function create () {
const productInstance = await client.findOne(inventory.class.Product, { _id: doc.attachedTo as Ref<Product> })
if (productInstance === undefined) {
throw new Error('product not found')
}
await client.addCollection(inventory.class.Variant, doc.space, doc.attachedTo, productInstance._class, 'variants', {
name: doc.name,
sku: doc.sku
})
}
</script>
<Card
label={inventory.string.CreateVariant}
okAction={create}
canSave={doc.name.trim().length > 0 && doc.sku.trim().length > 0}
bind:space={doc.space}
on:close={() => {
dispatch('close')
}}
>
<Grid column={1} rowGap={1.75}>
<EditBox label={inventory.string.Variant} bind:value={doc.name} placeholder="Variant" maxWidth={'16rem'} focus />
<EditBox label={inventory.string.SKU} bind:value={doc.sku} placeholder="SKU" maxWidth={'16rem'} />
</Grid>
</Card>

View File

@ -0,0 +1,152 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 core, { Doc, Ref } from '@anticrm/core'
import { Category, Product } from '@anticrm/inventory'
import { Panel } from '@anticrm/panel'
import {
AttributesBar,
createQuery,
getAttributePresenterClass,
getClient,
KeyedAttribute
} from '@anticrm/presentation'
import { AnyComponent, Component } from '@anticrm/ui'
import view from '@anticrm/view'
import { createEventDispatcher } from 'svelte'
import inventory from '../plugin'
export let _id: Ref<Product>
let object: Product
let rightSection: AnyComponent | undefined
const fullSize: boolean = true
let category: Category | undefined
const client = getClient()
const hierarchy = client.getHierarchy()
const docKeys: Set<string> = new Set<string>(hierarchy.getAllAttributes(core.class.AttachedDoc).keys())
const query = createQuery()
$: _id && update(_id)
function update (id: Ref<Product>) {
query.query(inventory.class.Product, { _id: id }, (result) => {
object = result[0]
client.findOne(inventory.class.Category, { _id: object.attachedTo as Ref<Category> }).then((res) => {
category = res
})
updateKeys(['comments'])
})
}
let keys: KeyedAttribute[] = []
let collectionKeys: KeyedAttribute[] = []
const dispatch = createEventDispatcher()
function filterKeys (keys: KeyedAttribute[], ignoreKeys: string[]): KeyedAttribute[] {
keys = keys.filter((k) => !docKeys.has(k.key))
keys = keys.filter((k) => !ignoreKeys.includes(k.key))
return keys
}
function getFiltredKeys (ignoreKeys: string[]): KeyedAttribute[] {
const keys = [...hierarchy.getAllAttributes(object._class).entries()]
.filter(([, value]) => value.hidden !== true)
.map(([key, attr]) => ({ key, attr }))
return filterKeys(keys, ignoreKeys)
}
function updateKeys (ignoreKeys: string[]): void {
const filtredKeys = getFiltredKeys(ignoreKeys)
keys = collectionsFilter(filtredKeys, false)
collectionKeys = collectionsFilter(filtredKeys, true)
}
function collectionsFilter (keys: KeyedAttribute[], get: boolean): KeyedAttribute[] {
const result: KeyedAttribute[] = []
for (const key of keys) {
if (isCollectionAttr(key) === get) result.push(key)
}
return result
}
function isCollectionAttr (key: KeyedAttribute): boolean {
return hierarchy.isDerived(key.attr.type._class, core.class.Collection)
}
async function getCollectionEditor (key: KeyedAttribute): Promise<AnyComponent> {
const attrClass = getAttributePresenterClass(key.attr)
const clazz = client.getHierarchy().getClass(attrClass)
const editorMixin = client.getHierarchy().as(clazz, view.mixin.AttributeEditor)
return editorMixin.editor
}
function getCollectionCounter (object: Doc, key: KeyedAttribute): number {
if (client.getHierarchy().isMixin(key.attr.attributeOf)) {
return (client.getHierarchy().as(object, key.attr.attributeOf) as any)[key.key]
}
return (object as any)[key.key] ?? 0
}
</script>
{#if object !== undefined}
<Panel
icon={inventory.icon.Products}
title={object.name}
subtitle={category?.name}
{rightSection}
{fullSize}
{object}
on:close={() => {
dispatch('close')
}}
>
<div slot="subtitle">
{#if keys}
<AttributesBar {object} {keys} />
{/if}
</div>
{#each collectionKeys as collection}
<div class="collection">
{#await getCollectionEditor(collection) then is}
<Component
{is}
props={{
objectId: object._id,
_class: object._class,
space: object.space,
[collection.key]: getCollectionCounter(object, collection)
}}
/>
{/await}
</div>
{/each}
</Panel>
{/if}
<style lang="scss">
.main-editor {
display: flex;
justify-content: center;
flex-direction: column;
}
.collection + .collection {
margin-top: 3.5rem;
}
</style>

View File

@ -0,0 +1,139 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { Doc, Ref } from '@anticrm/core'
import { IconMoreV, showPopup } from '@anticrm/ui'
import { AttributeModel } from '@anticrm/view'
import inventory, { Category } from '@anticrm/inventory'
import HierarchyElement from './HierarchyElement.svelte'
import { ContextMenu } from '@anticrm/view-resources'
import Expand from './icons/Expand.svelte'
import Collapse from './icons/Collapse.svelte'
export let descendants: Map<Ref<Doc>, Category[]>
export let level: number = 1
export let model: AttributeModel[]
export let parent: Ref<Doc> = inventory.global.Category
let expanded: Set<Ref<Category>> = new Set<Ref<Category>>()
function getValue (doc: Category, key: string): any {
if (key.length === 0) {
return doc
}
const path = key.split('.')
const len = path.length
let obj = doc as any
for (let i = 0; i < len; i++) {
obj = obj?.[path[i]]
}
return obj ?? ''
}
const showMenu = async (ev: MouseEvent, object: Category): Promise<void> => {
showPopup(ContextMenu, { object }, ev.target as HTMLElement)
}
function click (id: Ref<Category>): void {
if (!descendants.has(id)) return
if (expanded.has(id)) {
expanded.delete(id)
} else {
expanded.add(id)
}
expanded = expanded
}
$: style = `margin-left: ${level * 1.5}rem;`
</script>
{#each descendants.get(parent) ?? [] as object, row (object._id)}
<tr class="tr-body">
{#each model as attribute, cell}
{#if !cell}
<td>
<div class="firstCell" {style}>
{#if descendants.has(object._id)}
<div class="expand" on:click={(ev) => click(object._id)}>
{#if expanded.has(object._id)}
<Expand size={'small'} />
{:else}
<Collapse size={'small'} />
{/if}
</div>
{/if}
<svelte:component this={attribute.presenter} value={getValue(object, attribute.key)} {...attribute.props} />
<div class="menuRow" on:click={(ev) => showMenu(ev, object)}><IconMoreV size={'small'} /></div>
</div>
</td>
{:else}
<td>
<svelte:component this={attribute.presenter} value={getValue(object, attribute.key)} {...attribute.props} />
</td>
{/if}
{/each}
</tr>
{#if expanded.has(object._id)}
<HierarchyElement {descendants} {model} level={level + 1} parent={object._id} />
{/if}
{/each}
<style lang="scss">
.firstCell {
display: flex;
align-items: center;
.menuRow {
visibility: hidden;
margin-left: 0.5rem;
}
.expand {
margin-left: -1.5rem;
margin-right: 0.5rem;
}
.expand,
.menuRow {
opacity: 0.6;
cursor: pointer;
&:hover {
opacity: 1;
}
}
}
td {
padding: 0.5rem 1.5rem;
text-align: left;
&:first-child {
padding-left: 2.5rem;
}
&:last-child {
padding-right: 1.5rem;
}
}
.tr-body {
height: 3.25rem;
color: var(--theme-caption-color);
border-bottom: 1px solid var(--theme-button-border-hovered);
&:hover .firstCell .menuRow {
visibility: visible;
}
&:last-child {
border-bottom: none;
}
&:hover {
background-color: var(--theme-table-bg-hover);
}
}
</style>

View File

@ -0,0 +1,109 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { Class, Doc, DocumentQuery, FindOptions, Ref, SortingOrder } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import { Label, Loading } from '@anticrm/ui'
import { buildModel } from '@anticrm/view-resources'
import { Category } from '@anticrm/inventory'
import HierarchyElement from './HierarchyElement.svelte'
export let _class: Ref<Class<Category>>
export let query: DocumentQuery<Category> = {}
export let options: FindOptions<Category> | undefined = undefined
export let config: string[] = ['', 'modifiedOn']
let objects: Category[]
let descendants: Map<Ref<Doc>, Category[]> = new Map<Ref<Doc>, Category[]>()
const q = createQuery()
async function update (_class: Ref<Class<Category>>, query: DocumentQuery<Category>, options?: FindOptions<Category>) {
q.query(
_class,
query,
(result) => {
objects = result
updateDescendants()
},
{ sort: { name: SortingOrder.Ascending }, ...options, limit: 200 }
)
}
$: update(_class, query, options)
function updateDescendants (): void {
descendants.clear()
for (const doc of objects) {
const current = descendants.get(doc.attachedTo)
if (!current) {
descendants.set(doc.attachedTo, [doc])
} else {
current.push(doc)
descendants.set(doc.attachedTo, current)
}
}
descendants = descendants
}
const client = getClient()
</script>
{#await buildModel({ client, _class, keys: config, options })}
<Loading />
{:then model}
<table class="table-body">
<thead>
<tr class="tr-head">
{#each model as attribute}
<th>
<div class="flex-row-center whitespace-nowrap">
<Label label={attribute.label} />
</div>
</th>
{/each}
</tr>
</thead>
{#if objects}
<tbody>
<HierarchyElement {model} {descendants} />
</tbody>
{/if}
</table>
{/await}
<style lang="scss">
.table-body {
width: 100%;
}
th {
height: 2.5rem;
font-weight: 500;
font-size: 0.75rem;
color: var(--theme-content-dark-color);
box-shadow: inset 0 -1px 0 0 var(--theme-bg-focused-color);
user-select: none;
z-index: 5;
padding: 0.5rem 1.5rem;
text-align: left;
&:first-child {
padding-left: 2.5rem;
}
&:last-child {
padding-right: 1.5rem;
}
}
</style>

View File

@ -0,0 +1,50 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { Product } from '@anticrm/inventory'
import { closeTooltip, Icon, showPopup } from '@anticrm/ui'
import EditProduct from './EditProduct.svelte'
import inventory from '../plugin'
export let value: Product
function show () {
closeTooltip()
showPopup(EditProduct, { _id: value._id }, 'full')
}
</script>
{#if value}
<div class="sm-tool-icon container" on:click={show}>
<Icon icon={inventory.icon.Products} size="medium" />
<div class="overflow-label name">{value.name}</div>
</div>
{/if}
<style lang="scss">
.container {
.name {
margin-left: 0.5rem;
font-weight: 500;
text-align: left;
color: var(--theme-content-accent-color);
}
&:hover .name {
text-decoration: underline;
color: var(--theme-caption-color);
}
}
</style>

View File

@ -0,0 +1,132 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { Button, EditWithIcon, Icon, IconSearch, Label, ScrollBox, showPopup } from '@anticrm/ui'
import CreateProduct from './CreateProduct.svelte'
import inventory from '../plugin'
import { Table } from '@anticrm/view-resources'
import { getClient } from '@anticrm/presentation'
import view, { Viewlet } from '@anticrm/view'
let search = ''
$: resultQuery = search === '' ? {} : { $search: search }
const client = getClient()
const tableDescriptor = client.findOne<Viewlet>(view.class.Viewlet, {
attachTo: inventory.class.Product,
descriptor: view.viewlet.Table
})
function showCreateDialog (ev: Event) {
showPopup(CreateProduct, { space: inventory.space.Products }, ev.target as HTMLElement)
}
</script>
<div class="products-header-container">
<div class="header-container">
<div class="flex-row-center">
<span class="icon"><Icon icon={inventory.icon.Products} size={'small'} /></span>
<span class="label"><Label label={inventory.string.Products} /></span>
</div>
</div>
<EditWithIcon
icon={IconSearch}
placeholder={'Search'}
bind:value={search}
on:change={() => {
resultQuery = {}
}}
/>
<Button
label={inventory.string.CreateProductShort}
primary={true}
size={'small'}
on:click={(ev) => showCreateDialog(ev)}
/>
</div>
<div class="container">
<div class="panel-component">
<ScrollBox vertical stretch noShift>
{#await tableDescriptor then descr}
{#if descr}
<Table
_class={inventory.class.Product}
config={descr.config}
options={descr.options}
query={resultQuery}
enableChecking
/>
{/if}
{/await}
</ScrollBox>
</div>
</div>
<style lang="scss">
.container {
display: flex;
height: 100%;
padding-bottom: 1.25rem;
.panel-component {
flex-grow: 1;
display: flex;
flex-direction: column;
margin-right: 1rem;
height: 100%;
border-radius: 1.25rem;
background-color: var(--theme-bg-color);
overflow: hidden;
}
}
.products-header-container {
display: grid;
grid-template-columns: auto;
grid-auto-flow: column;
grid-auto-columns: min-content;
gap: 0.75rem;
align-items: center;
padding: 0 1.75rem 0 2.5rem;
height: 4rem;
min-height: 4rem;
white-space: nowrap;
.header-container {
display: flex;
flex-direction: column;
flex-grow: 1;
.icon {
margin-right: 0.5rem;
opacity: 0.6;
}
.label {
flex-grow: 1;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 35rem;
}
.label {
font-weight: 500;
font-size: 1rem;
color: var(--theme-caption-color);
}
}
}
</style>

View File

@ -0,0 +1,26 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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 { Variant } from '@anticrm/inventory'
export let value: Variant
</script>
{#if value}
<div class="sm-tool-icon">
{value.name}
</div>
{/if}

View File

@ -0,0 +1,75 @@
<!--
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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 type { Doc, Ref } from '@anticrm/core'
import { Icon, CircleButton, IconAdd, Label, showPopup } from '@anticrm/ui'
import { Table } from '@anticrm/view-resources'
import inventory from '../plugin'
import CreateVariant from './CreateVariant.svelte'
export let objectId: Ref<Doc>
export let variants: number
const create = (ev: MouseEvent): void => showPopup(CreateVariant, { product: objectId }, ev.target as HTMLElement)
</script>
<div class="variants-container">
<div class="flex-row-center">
<div class="title"><Label label={inventory.string.Variants} /></div>
<CircleButton icon={IconAdd} size={'small'} selected on:click={create} />
</div>
{#if variants > 0}
<Table
_class={inventory.class.Variant}
config={['', 'sku', 'modifiedOn']}
options={{}}
query={{ attachedTo: objectId }}
loadingProps={{ length: variants }}
/>
{:else}
<div class="flex-col-center mt-5 create-container">
<Icon size={'large'} icon={inventory.icon.Variant} />
<div class="small-text content-dark-color mt-2">
<Label label={inventory.string.NoVariantsForProduct} />
</div>
<div class="small-text">
<a href={'#'} on:click={create}><Label label={inventory.string.CreateVariant} /></a>
</div>
</div>
{/if}
</div>
<style lang="scss">
.variants-container {
display: flex;
flex-direction: column;
.title {
margin-right: 0.75rem;
font-weight: 500;
font-size: 1.25rem;
color: var(--theme-caption-color);
}
}
.create-container {
padding: 1rem;
color: var(--theme-caption-color);
background: var(--theme-bg-accent-color);
border: 1px solid var(--theme-bg-accent-color);
border-radius: 0.75rem;
}
</style>

View File

@ -0,0 +1,8 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 8.25L6 3.75L10.5 8.25" stroke={fill} />
</svg>

View File

@ -0,0 +1,8 @@
<script lang="ts">
export let size: 'small' | 'medium' | 'large'
const fill: string = 'currentColor'
</script>
<svg class="svg-{size}" fill="none" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 3.75L6 8.25L1.5 3.75" stroke={fill} />
</svg>

View File

@ -0,0 +1,46 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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.
//
import { Resources } from '@anticrm/platform'
import Categories from './components/Categories.svelte'
import CreateCategory from './components/CreateCategory.svelte'
import CategoryPresenter from './components/CategoryPresenter.svelte'
import Products from './components/Products.svelte'
import ProductPresenter from './components/ProductPresenter.svelte'
import EditProduct from './components/EditProduct.svelte'
import Variants from './components/Variants.svelte'
import VariantPresenter from './components/VariantPresenter.svelte'
import { Doc } from '@anticrm/core'
import { showPopup } from '@anticrm/ui'
async function createSubcategory (object: Doc): Promise<void> {
showPopup(CreateCategory, { attachedTo: object._id })
}
export default async (): Promise<Resources> => ({
actionImpl: {
CreateSubcategory: createSubcategory
},
component: {
Categories,
CategoryPresenter,
Products,
ProductPresenter,
EditProduct,
Variants,
VariantPresenter
}
})

View File

@ -0,0 +1,43 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { IntlString, mergeIds, StatusCode } from '@anticrm/platform'
import inventory, { inventoryId } from '@anticrm/inventory'
export default mergeIds(inventoryId, inventory, {
status: {
CategoryRequired: '' as StatusCode,
NameRequired: '' as StatusCode
},
string: {
Categories: '' as IntlString,
Category: '' as IntlString,
CreateCategoryShort: '' as IntlString,
CreateCategory: '' as IntlString,
CreateSubcategory: '' as IntlString,
Inventory: '' as IntlString,
CreateProductShort: '' as IntlString,
CreateProduct: '' as IntlString,
Products: '' as IntlString,
Product: '' as IntlString,
SKU: '' as IntlString,
Variant: '' as IntlString,
Variants: '' as IntlString,
NoVariantsForProduct: '' as IntlString,
CreateVariant: '' as IntlString
}
})

View File

@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};

View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,32 @@
{
"name": "@anticrm/inventory",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/core": "~0.6.11",
"@anticrm/platform": "~0.6.5"
}
}

View File

@ -0,0 +1,76 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 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.
//
import { AttachedDoc, Class, Ref, Space } from '@anticrm/core'
import type { Asset, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
/**
* @public
*/
export interface Category extends AttachedDoc {
name: string
}
/**
* @public
*/
export interface Product extends AttachedDoc {
attachments?: number
photos?: number
variants?: number
name: string
}
/**
* @public
*/
export interface Variant extends AttachedDoc {
name: string
sku: string
}
/**
* @public
*/
export const inventoryId = 'inventory' as Plugin
/**
* @public
*/
const inventory = plugin(inventoryId, {
class: {
Product: '' as Ref<Class<Product>>,
Category: '' as Ref<Class<Category>>,
Variant: '' as Ref<Class<Variant>>
},
icon: {
InventoryApplication: '' as Asset,
Products: '' as Asset,
Categories: '' as Asset,
Variant: '' as Asset
},
global: {
// Global category root, if not attached to some other object.
Category: '' as Ref<Category>
},
space: {
Category: '' as Ref<Space>,
Products: '' as Ref<Space>
}
})
export default inventory

View File

@ -0,0 +1,9 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"lib": ["esnext", "dom"]
}
}

View File

@ -921,5 +921,25 @@
"projectFolder": "plugins/gmail-resources",
"shouldPublish": true
},
{
"packageName": "@anticrm/model-inventory",
"projectFolder": "models/inventory",
"shouldPublish": true
},
{
"packageName": "@anticrm/inventory",
"projectFolder": "plugins/inventory",
"shouldPublish": true
},
{
"packageName": "@anticrm/inventory-assets",
"projectFolder": "plugins/inventory-assets",
"shouldPublish": true
},
{
"packageName": "@anticrm/inventory-resources",
"projectFolder": "plugins/inventory-resources",
"shouldPublish": true
}
]
}