sync form work
This commit is contained in:
		@@ -46,7 +46,7 @@
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
            text-overflow: ellipsis;
 | 
			
		||||
            cursor: pointer;
 | 
			
		||||
            color: $foreColor;
 | 
			
		||||
            color: var(--color);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,7 +5,6 @@
 | 
			
		||||
$headerHeight: 26px;
 | 
			
		||||
$caretWidth: 26px;
 | 
			
		||||
$dropItemHeight: 30px;
 | 
			
		||||
$borderRadius: 2px;
 | 
			
		||||
$scrollBarSize: 4px;
 | 
			
		||||
 | 
			
		||||
$searchBarHeight: 36px;
 | 
			
		||||
@@ -21,16 +20,16 @@ $listMaxHeight: 210px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
 | 
			
		||||
    >.drop-header {
 | 
			
		||||
        border: 1px solid $borderColor;
 | 
			
		||||
        border-radius: $borderRadius;
 | 
			
		||||
        background-color: $bgColor;
 | 
			
		||||
        border: 1px solid var(--border-color);
 | 
			
		||||
        border-radius: var(--border-radius);
 | 
			
		||||
        background-color: var(--bg-color);
 | 
			
		||||
        display: flex;
 | 
			
		||||
        height: $headerHeight;
 | 
			
		||||
        transition: border-color .2s;
 | 
			
		||||
 | 
			
		||||
        &:focus,
 | 
			
		||||
        &:hover {
 | 
			
		||||
            border-color: $focusColor;
 | 
			
		||||
            border-color: var(--focus-color);
 | 
			
		||||
            // box-shadow: 0 0 3px 1px rgba(0, 0, 0, .2);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -52,7 +51,7 @@ $listMaxHeight: 210px;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            &::-webkit-scrollbar-thumb {
 | 
			
		||||
                border-radius: $borderRadius;
 | 
			
		||||
                border-radius: var(--border-radius);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            >span {
 | 
			
		||||
@@ -64,7 +63,7 @@ $listMaxHeight: 210px;
 | 
			
		||||
                line-height: 20px;
 | 
			
		||||
                background-color: white;
 | 
			
		||||
                font-size: $tinySize;
 | 
			
		||||
                border-radius: $borderRadius;
 | 
			
		||||
                border-radius: var(--border-radius);
 | 
			
		||||
                cursor: pointer;
 | 
			
		||||
                position: relative;
 | 
			
		||||
 | 
			
		||||
@@ -100,7 +99,7 @@ $listMaxHeight: 210px;
 | 
			
		||||
                padding: 0 4px;
 | 
			
		||||
                font-weight: 400;
 | 
			
		||||
                font-size: $smallSize;
 | 
			
		||||
                color: $foreColor;
 | 
			
		||||
                color: var(--color);
 | 
			
		||||
            }
 | 
			
		||||
        }*/
 | 
			
		||||
 | 
			
		||||
@@ -146,11 +145,11 @@ $listMaxHeight: 210px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &.disabled {
 | 
			
		||||
            border-color: $disabledBgColor;
 | 
			
		||||
            color: $disabledForeColor;
 | 
			
		||||
            border-color: var(--disabled-bg-color);
 | 
			
		||||
            color: var(--disabled-color);
 | 
			
		||||
 | 
			
		||||
            &:focus {
 | 
			
		||||
                border-color: $disabledBgColor;
 | 
			
		||||
                border-color: var(--disabled-bg-color);
 | 
			
		||||
                // box-shadow: none;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -167,13 +166,13 @@ $listMaxHeight: 210px;
 | 
			
		||||
        opacity: 0;
 | 
			
		||||
        transform: scaleY(0);
 | 
			
		||||
        transform-origin: top;
 | 
			
		||||
        background-color: $bgColor;
 | 
			
		||||
        background-color: var(--bg-color);
 | 
			
		||||
        top: calc($headerHeight + 2px);
 | 
			
		||||
        z-index: 2;
 | 
			
		||||
        transition: transform 120ms ease, opacity 120ms ease, visibility 120ms ease;
 | 
			
		||||
        width: calc(100% + 2px);
 | 
			
		||||
        box-sizing: border-box;
 | 
			
		||||
        /*border: 1px solid $borderColor;
 | 
			
		||||
        /*border: 1px solid var(--border-color);
 | 
			
		||||
        border-top-width: 0;*/
 | 
			
		||||
        box-shadow: 0 3px 6px -4px rgba(0, 0, 0, .12), 0 6px 16px 0 rgba(0, 0, 0, .08), 0 9px 28px 8px rgba(0, 0, 0, .05);
 | 
			
		||||
        left: -1px;
 | 
			
		||||
@@ -206,15 +205,15 @@ $listMaxHeight: 210px;
 | 
			
		||||
                width: 100%;
 | 
			
		||||
                height: $searchInputHeight;
 | 
			
		||||
                outline: none;
 | 
			
		||||
                border: 1px solid $borderColor;
 | 
			
		||||
                border-radius: $borderRadius;
 | 
			
		||||
                border: 1px solid var(--border-color);
 | 
			
		||||
                border-radius: var(--border-radius);
 | 
			
		||||
                padding: 0 6px 0 22px;
 | 
			
		||||
                color: $foreColor;
 | 
			
		||||
                color: var(--color);
 | 
			
		||||
                transition: border-color .2s;
 | 
			
		||||
 | 
			
		||||
                &:hover,
 | 
			
		||||
                &:focus {
 | 
			
		||||
                    border-color: $focusColor;
 | 
			
		||||
                    border-color: var(--focus-color);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // &:focus {
 | 
			
		||||
@@ -245,7 +244,7 @@ $listMaxHeight: 210px;
 | 
			
		||||
            @include scrollbar();
 | 
			
		||||
 | 
			
		||||
            &.filtered>li:first-child {
 | 
			
		||||
                background-color: $hoverColor;
 | 
			
		||||
                background-color: var(--hover-color);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            >li {
 | 
			
		||||
@@ -261,7 +260,7 @@ $listMaxHeight: 210px;
 | 
			
		||||
 | 
			
		||||
                &:hover,
 | 
			
		||||
                &.selected {
 | 
			
		||||
                    background-color: $hoverColor;
 | 
			
		||||
                    background-color: var(--hover-color);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                >.checkbox-wrapper {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,3 @@
 | 
			
		||||
$boxBorderColor: #999898;
 | 
			
		||||
$boxCheckedColor: #1890ff;
 | 
			
		||||
$boxDisabledColor: #d9d9d9;
 | 
			
		||||
 | 
			
		||||
@mixin check-box() {
 | 
			
		||||
    .check-box-inner {
 | 
			
		||||
        position: relative;
 | 
			
		||||
@@ -10,7 +6,7 @@ $boxDisabledColor: #d9d9d9;
 | 
			
		||||
        width: 14px;
 | 
			
		||||
        height: 14px;
 | 
			
		||||
        background-color: #fff;
 | 
			
		||||
        border: 1px solid $boxBorderColor;
 | 
			
		||||
        border: 1px solid var(--box-color);
 | 
			
		||||
        user-select: none;
 | 
			
		||||
        border-radius: 2px;
 | 
			
		||||
        transition: all .2s;
 | 
			
		||||
@@ -33,8 +29,8 @@ $boxDisabledColor: #d9d9d9;
 | 
			
		||||
        display: none;
 | 
			
		||||
 | 
			
		||||
        &:checked+.check-box-inner {
 | 
			
		||||
            border-color: $boxCheckedColor;
 | 
			
		||||
            background-color: $boxCheckedColor;
 | 
			
		||||
            border-color: var(--link-color);
 | 
			
		||||
            background-color: var(--link-color);
 | 
			
		||||
 | 
			
		||||
            >svg {
 | 
			
		||||
                transform: scale(1);
 | 
			
		||||
@@ -44,17 +40,17 @@ $boxDisabledColor: #d9d9d9;
 | 
			
		||||
 | 
			
		||||
        &:disabled {
 | 
			
		||||
            &+.check-box-inner {
 | 
			
		||||
                border-color: $boxDisabledColor;
 | 
			
		||||
                border-color: var(--disabled-box-color);
 | 
			
		||||
                cursor: default;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            &:checked+.check-box-inner {
 | 
			
		||||
                border-color: $boxDisabledColor;
 | 
			
		||||
                background-color: $boxDisabledColor;
 | 
			
		||||
                border-color: var(--disabled-box-color);
 | 
			
		||||
                background-color: var(--disabled-box-color);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            &~span {
 | 
			
		||||
                color: $boxDisabledColor;
 | 
			
		||||
                color: var(--disabled-box-color);
 | 
			
		||||
                cursor: default;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,3 @@
 | 
			
		||||
@keyframes loading-spinner {
 | 
			
		||||
    0% {
 | 
			
		||||
        transform: rotate(0deg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    100% {
 | 
			
		||||
        transform: rotate(359deg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.grid {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
@@ -17,8 +7,6 @@
 | 
			
		||||
 | 
			
		||||
    & {
 | 
			
		||||
        --hover-bg-color: lightyellow;
 | 
			
		||||
        --link-fore-color: #1890ff;
 | 
			
		||||
        --link-disabled-color: #d9d9d9;
 | 
			
		||||
        --header-border-color: #adaba9;
 | 
			
		||||
        --header-bg-color: #fafafa;
 | 
			
		||||
        --header-fore-color: #000;
 | 
			
		||||
@@ -32,8 +20,6 @@
 | 
			
		||||
        --row-active-bg-color: #fafafa;
 | 
			
		||||
        --row-selected-bg-color: #e6f2fb;
 | 
			
		||||
        --text-disabled-color: gray;
 | 
			
		||||
        --loading-bg-color: hsla(0, 0%, 100%, .4);
 | 
			
		||||
        --loading-fore-color: rgba(0, 0, 0, .2);
 | 
			
		||||
 | 
			
		||||
        --font-size: .8125rem;
 | 
			
		||||
        --line-height: 36px;
 | 
			
		||||
@@ -260,11 +246,16 @@
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    .checkbox-wrapper .check-box-inner {
 | 
			
		||||
                    .checkbox-wrapper {
 | 
			
		||||
                        display: flex;
 | 
			
		||||
                        justify-content: center;
 | 
			
		||||
 | 
			
		||||
                        &,
 | 
			
		||||
                        >svg {
 | 
			
		||||
                            transition: none;
 | 
			
		||||
                        .check-box-inner {
 | 
			
		||||
 | 
			
		||||
                            &,
 | 
			
		||||
                            >svg {
 | 
			
		||||
                                transition: none;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
@@ -293,7 +284,7 @@
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    .icon {
 | 
			
		||||
                    .col-icon {
 | 
			
		||||
                        display: flex;
 | 
			
		||||
                        cursor: pointer;
 | 
			
		||||
                        height: var(--line-height);
 | 
			
		||||
@@ -304,7 +295,7 @@
 | 
			
		||||
                        >svg {
 | 
			
		||||
                            width: 16px;
 | 
			
		||||
                            height: 16px;
 | 
			
		||||
                            fill: var(--link-fore-color);
 | 
			
		||||
                            fill: var(--primary-color);
 | 
			
		||||
                            transition: opacity .12s ease;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										132
									
								
								css/popup.scss
									
									
									
									
									
								
							
							
						
						
									
										132
									
								
								css/popup.scss
									
									
									
									
									
								
							@@ -2,7 +2,7 @@
 | 
			
		||||
@import './variables/definition.scss';
 | 
			
		||||
 | 
			
		||||
$headerLineHeight: 24px;
 | 
			
		||||
$buttonHeight: 36px;
 | 
			
		||||
$buttonHeight: 28px;
 | 
			
		||||
 | 
			
		||||
.popup-mask {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
@@ -15,17 +15,23 @@ $buttonHeight: 36px;
 | 
			
		||||
    z-index: 200;
 | 
			
		||||
    transition: opacity .12s ease;
 | 
			
		||||
 | 
			
		||||
    &.active .popup-container {
 | 
			
		||||
    & {
 | 
			
		||||
        --corner-radius: 6px;
 | 
			
		||||
        --loading-size: 20px;
 | 
			
		||||
        --loading-border-radius: 10px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.popup-active .popup-container {
 | 
			
		||||
        transform: scale(1.1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .popup-container {
 | 
			
		||||
        min-width: 400px;
 | 
			
		||||
        max-width: 800px;
 | 
			
		||||
        background-color: $bgColor;
 | 
			
		||||
        border-radius: 2px;
 | 
			
		||||
        background-color: var(--bg-color);
 | 
			
		||||
        border-radius: var(--corner-radius);
 | 
			
		||||
        box-shadow: 0 2px 8px rgba(0 0 0 /11%);
 | 
			
		||||
        transition: all .12s ease;
 | 
			
		||||
        transition: opacity .12s ease, transform .12s ease;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
@@ -33,11 +39,13 @@ $buttonHeight: 36px;
 | 
			
		||||
        .popup-header {
 | 
			
		||||
            flex: 0 0 auto;
 | 
			
		||||
            padding: 10px 12px 6px;
 | 
			
		||||
            border-radius: var(--corner-radius) var(--corner-radius) 0 0;
 | 
			
		||||
            line-height: $headerLineHeight;
 | 
			
		||||
            user-select: none;
 | 
			
		||||
            background-color: var(--title-bg-color);
 | 
			
		||||
            color: var(--title-color);
 | 
			
		||||
            display: flex;
 | 
			
		||||
            align-items: center;
 | 
			
		||||
 | 
			
		||||
            >div {
 | 
			
		||||
                flex: 1 1 auto;
 | 
			
		||||
@@ -48,6 +56,7 @@ $buttonHeight: 36px;
 | 
			
		||||
                flex: 0 0 auto;
 | 
			
		||||
                width: $headerLineHeight;
 | 
			
		||||
                height: $headerLineHeight;
 | 
			
		||||
                fill: var(--title-color);
 | 
			
		||||
                padding: 4px;
 | 
			
		||||
                cursor: pointer;
 | 
			
		||||
                box-sizing: border-box;
 | 
			
		||||
@@ -63,6 +72,109 @@ $buttonHeight: 36px;
 | 
			
		||||
            margin: 6px 10px;
 | 
			
		||||
            flex: 1 1 auto;
 | 
			
		||||
            line-height: $headerLineHeight;
 | 
			
		||||
            position: relative;
 | 
			
		||||
            min-height: 100px;
 | 
			
		||||
 | 
			
		||||
            >.popup-loading {
 | 
			
		||||
                position: absolute;
 | 
			
		||||
                @include inset(0, 0, -46px, 0);
 | 
			
		||||
                visibility: hidden;
 | 
			
		||||
                opacity: 0;
 | 
			
		||||
                transition: visibility 0s linear .12s, opacity .12s ease;
 | 
			
		||||
                background-color: var(--loading-bg-color);
 | 
			
		||||
                display: flex;
 | 
			
		||||
                justify-content: center;
 | 
			
		||||
                align-items: center;
 | 
			
		||||
                z-index: 1;
 | 
			
		||||
 | 
			
		||||
                >div {
 | 
			
		||||
                    background-color: var(--loading-fore-color);
 | 
			
		||||
                    border-radius: var(--loading-border-radius);
 | 
			
		||||
 | 
			
		||||
                    >svg {
 | 
			
		||||
                        width: var(--loading-size);
 | 
			
		||||
                        height: var(--loading-size);
 | 
			
		||||
                        padding: 20px;
 | 
			
		||||
                        animation: loading-spinner 1.2s infinite linear;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            >.message-wrapper {
 | 
			
		||||
                display: flex;
 | 
			
		||||
                margin: 10px;
 | 
			
		||||
 | 
			
		||||
                >svg {
 | 
			
		||||
                    width: 40px;
 | 
			
		||||
                    height: 40px;
 | 
			
		||||
 | 
			
		||||
                    &+span {
 | 
			
		||||
                        padding-left: 16px;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .setting-wrapper {
 | 
			
		||||
                --line-height: 28px;
 | 
			
		||||
 | 
			
		||||
                >.setting-item {
 | 
			
		||||
                    display: flex;
 | 
			
		||||
                    align-items: center;
 | 
			
		||||
                    line-height: var(--line-height);
 | 
			
		||||
                    margin: 4px 0;
 | 
			
		||||
 | 
			
		||||
                    >.setting-label {
 | 
			
		||||
                        flex: 0 0 auto;
 | 
			
		||||
                        width: 120px;
 | 
			
		||||
                        text-align: right;
 | 
			
		||||
                        white-space: nowrap;
 | 
			
		||||
                        overflow: hidden;
 | 
			
		||||
                        text-overflow: ellipsis;
 | 
			
		||||
                        padding-right: 10px;
 | 
			
		||||
 | 
			
		||||
                        &.setting-required::after {
 | 
			
		||||
                            content: '*';
 | 
			
		||||
                            color: var(--red-color);
 | 
			
		||||
                            font-weight: bold;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        &+* {
 | 
			
		||||
                            flex: 1 1 auto;
 | 
			
		||||
                            margin-right: 10px;
 | 
			
		||||
                            box-sizing: border-box;
 | 
			
		||||
                            height: var(--line-height);
 | 
			
		||||
                            line-height: var(--line-height);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        &+input[type="text"],
 | 
			
		||||
                        &+input[type="email"],
 | 
			
		||||
                        &+input[type="tel"],
 | 
			
		||||
                        &+textarea {
 | 
			
		||||
                            border: 1px solid var(--border-color);
 | 
			
		||||
                            border-radius: var(--border-radius);
 | 
			
		||||
                            text-indent: var(--text-indent);
 | 
			
		||||
                            transition: border-color .12s ease;
 | 
			
		||||
 | 
			
		||||
                            &:hover {
 | 
			
		||||
                                border-color: var(--focus-color);
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            &:focus,
 | 
			
		||||
                            &:focus-visible {
 | 
			
		||||
                                outline: none;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        &+.drop-wrapper>.drop-header>.drop-text {
 | 
			
		||||
                            font-size: 12px;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        &+.checkbox-wrapper {
 | 
			
		||||
                            padding: 0;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .popup-footer {
 | 
			
		||||
@@ -73,14 +185,18 @@ $buttonHeight: 36px;
 | 
			
		||||
            padding: 4px 10px 16px 2px;
 | 
			
		||||
 | 
			
		||||
            .popup-button {
 | 
			
		||||
                margin-left: 8px;
 | 
			
		||||
                margin-left: 12px;
 | 
			
		||||
                border: none;
 | 
			
		||||
                height: $buttonHeight;
 | 
			
		||||
                line-height: $buttonHeight;
 | 
			
		||||
                padding: 0 8px;
 | 
			
		||||
                min-width: 60px;
 | 
			
		||||
                color: var(--title-color);
 | 
			
		||||
                border-radius: var(--corner-radius);
 | 
			
		||||
                padding: 0 16px;
 | 
			
		||||
                box-sizing: border-box;
 | 
			
		||||
                min-width: 70px;
 | 
			
		||||
                text-align: center;
 | 
			
		||||
                cursor: pointer;
 | 
			
		||||
                user-select: none;
 | 
			
		||||
                background-color: var(--title-bg-color);
 | 
			
		||||
                transition: opacity .12s ease;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,37 @@
 | 
			
		||||
// color
 | 
			
		||||
$borderColor: #d9d9d9;
 | 
			
		||||
$focusColor: #b9b9b9;
 | 
			
		||||
$disabledBgColor: #e9e9e9;
 | 
			
		||||
$disabledForeColor: #aaa;
 | 
			
		||||
$hoverColor: #eee;
 | 
			
		||||
$bgColor: #fff;
 | 
			
		||||
$foreColor: #201f1e;
 | 
			
		||||
 | 
			
		||||
// dimension
 | 
			
		||||
$mediumSize: .875rem; // 14px
 | 
			
		||||
$smallSize: .8125rem; // 13px
 | 
			
		||||
$tinySize: .75rem; // 12px
 | 
			
		||||
$tinySize: .75rem; // 12px
 | 
			
		||||
 | 
			
		||||
// animation
 | 
			
		||||
@keyframes loading-spinner {
 | 
			
		||||
    0% {
 | 
			
		||||
        transform: rotate(0deg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    100% {
 | 
			
		||||
        transform: rotate(359deg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
    --color: #201f1e;
 | 
			
		||||
    --red-color: red;
 | 
			
		||||
    --bg-color: #fff;
 | 
			
		||||
    --title-color: #fff;
 | 
			
		||||
    --title-bg-color: rgb(68, 114, 196);
 | 
			
		||||
    --border-color: #d9d9d9;
 | 
			
		||||
    --focus-color: #b9b9b9;
 | 
			
		||||
    --disabled-bg-color: #e9e9e9;
 | 
			
		||||
    --disabled-color: #aaa;
 | 
			
		||||
    --box-color: #999898;
 | 
			
		||||
    --disabled-box-color: #d9d9d9;
 | 
			
		||||
    --hover-color: #eee;
 | 
			
		||||
    --link-color: #1890ff;
 | 
			
		||||
    --primary-color: rgb(123,28,33);
 | 
			
		||||
    --loading-bg-color: hsla(0, 0%, 100%, .4);
 | 
			
		||||
    --loading-fore-color: rgba(0, 0, 0, .2);
 | 
			
		||||
 | 
			
		||||
    --border-radius: 2px;
 | 
			
		||||
    --text-indent: 4px;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										164
									
								
								lib/app/communications/contact.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								lib/app/communications/contact.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,164 @@
 | 
			
		||||
import { createElement } from "../../functions";
 | 
			
		||||
import { createCheckbox } from "../../ui/checkbox";
 | 
			
		||||
import Dropdown from "../../ui/dropdown";
 | 
			
		||||
import { createPopup, showAlert } from "../../ui/popup";
 | 
			
		||||
import { isEmail, nullOrEmpty, r } from "../../utility";
 | 
			
		||||
 | 
			
		||||
class Contact {
 | 
			
		||||
    #option;
 | 
			
		||||
    #refs;
 | 
			
		||||
 | 
			
		||||
    constructor(option = {}) {
 | 
			
		||||
        this.#option = option;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async show(parent = document.body) {
 | 
			
		||||
        const c = this.#option.contact;
 | 
			
		||||
        const contactName = createElement('input', input => {
 | 
			
		||||
            input.type = 'text';
 | 
			
		||||
            input.tabIndex = 1;
 | 
			
		||||
            input.maxLength = 200;
 | 
			
		||||
            input.autocomplete = 'off';
 | 
			
		||||
        });
 | 
			
		||||
        const preferences = new Dropdown({ tabindex: 2 });
 | 
			
		||||
        preferences.source = [
 | 
			
		||||
            { value: '0', text: r('text', 'Text') },
 | 
			
		||||
            { value: '1', text: r('email', 'Email') },
 | 
			
		||||
            { value: '2', text: r('phone', 'Phone') }
 | 
			
		||||
        ];
 | 
			
		||||
        const contactEmail = createElement('input', input => {
 | 
			
		||||
            input.type = 'email';
 | 
			
		||||
            input.tabIndex = 3;
 | 
			
		||||
            input.maxLength = 100;
 | 
			
		||||
            input.autocomplete = 'off';
 | 
			
		||||
        });
 | 
			
		||||
        const contactMobile = createElement('input', input => {
 | 
			
		||||
            input.type = 'tel';
 | 
			
		||||
            input.tabIndex = 4;
 | 
			
		||||
            input.maxLength = 50;
 | 
			
		||||
            input.autocomplete = 'off';
 | 
			
		||||
        });
 | 
			
		||||
        const checkOpt = createCheckbox({
 | 
			
		||||
            customerAttributes: {
 | 
			
		||||
                tabindex: 5
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        const contactNotes = createElement('textarea', txt => {
 | 
			
		||||
            txt.tabIndex = 6;
 | 
			
		||||
            txt.maxLength = 2000;
 | 
			
		||||
            txt.style.height = '100px';
 | 
			
		||||
        });
 | 
			
		||||
        const popup = createPopup(
 | 
			
		||||
            c == null ? r('addContact', 'Add Contact') : r('editContact', 'Edit Contact'),
 | 
			
		||||
            createElement('div', wrapper => {
 | 
			
		||||
                wrapper.className = 'setting-wrapper';
 | 
			
		||||
                wrapper.style.width = '500px';
 | 
			
		||||
            },
 | 
			
		||||
                createElement('div', 'setting-item',
 | 
			
		||||
                    createElement('span', 'setting-label setting-required', r('contactNameColon', 'Contact Name:')),
 | 
			
		||||
                    contactName
 | 
			
		||||
                ),
 | 
			
		||||
                createElement('div', 'setting-item',
 | 
			
		||||
                    createElement('span', 'setting-label', r('contactPreferencesColon', 'Contact Preferences:')),
 | 
			
		||||
                    preferences.create()
 | 
			
		||||
                ),
 | 
			
		||||
                createElement('div', 'setting-item',
 | 
			
		||||
                    createElement('span', 'setting-label', r('contactEmailColon', 'Email Address:')),
 | 
			
		||||
                    contactEmail
 | 
			
		||||
                ),
 | 
			
		||||
                createElement('div', 'setting-item',
 | 
			
		||||
                    createElement('span', 'setting-label', r('contactMobileColon', 'Mobile:')),
 | 
			
		||||
                    contactMobile
 | 
			
		||||
                ),
 | 
			
		||||
                createElement('div', 'setting-item',
 | 
			
		||||
                    createElement('span', 'setting-label', r('contactOptColon', 'Opt Out:')),
 | 
			
		||||
                    checkOpt
 | 
			
		||||
                ),
 | 
			
		||||
                createElement('div', 'setting-item',
 | 
			
		||||
                    createElement('span', 'setting-label', r('contactNotesColon', 'Notes:')),
 | 
			
		||||
                    contactNotes
 | 
			
		||||
                )
 | 
			
		||||
            ),
 | 
			
		||||
            {
 | 
			
		||||
                text: c == null ? r('addContactRecord', 'Add Contact Record') : r('editContactRecord', 'Edit Contact Record'),
 | 
			
		||||
                trigger: () => {
 | 
			
		||||
                    const item = this.prepare();
 | 
			
		||||
                    if (item == null) {
 | 
			
		||||
                        return false;
 | 
			
		||||
                    }
 | 
			
		||||
                    item.SaveToCustomer = 1;
 | 
			
		||||
                    if (typeof this.#option.onSave === 'function') {
 | 
			
		||||
                        return this.#option.onSave.call(this, item, c == null);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                text: r('workOrderOnly', 'Work Order Only'),
 | 
			
		||||
                trigger: () => {
 | 
			
		||||
                    const item = this.prepare();
 | 
			
		||||
                    if (item == null) {
 | 
			
		||||
                        return false;
 | 
			
		||||
                    }
 | 
			
		||||
                    item.SaveToCustomer = 0;
 | 
			
		||||
                    if (typeof this.#option.onSave === 'function') {
 | 
			
		||||
                        return this.#option.onSave.call(this, item, c == null);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            { text: r('cancel', 'Cancel') }
 | 
			
		||||
        )
 | 
			
		||||
        if (c != null) {
 | 
			
		||||
            contactName.value = c.Name;
 | 
			
		||||
            preferences.select(String(c.ContactPreference));
 | 
			
		||||
            contactEmail.value = c.Email;
 | 
			
		||||
            contactMobile.value = c.MobilePhone;
 | 
			
		||||
            checkOpt.querySelector('input').checked = c.OptOut;
 | 
			
		||||
            contactNotes.value = c.Notes;
 | 
			
		||||
        }
 | 
			
		||||
        this.#refs = {
 | 
			
		||||
            contactName,
 | 
			
		||||
            preferences,
 | 
			
		||||
            contactEmail,
 | 
			
		||||
            contactMobile,
 | 
			
		||||
            checkOpt,
 | 
			
		||||
            contactNotes
 | 
			
		||||
        };
 | 
			
		||||
        const result = await popup.show(parent);
 | 
			
		||||
        setTimeout(() => contactName.focus());
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    prepare() {
 | 
			
		||||
        const item = {
 | 
			
		||||
            'Id': this.#option.contact?.Id,
 | 
			
		||||
            'Name': this.#refs.contactName.value,
 | 
			
		||||
            'ContactPreference': this.#refs.preferences.selected.value,
 | 
			
		||||
            'Email': this.#refs.contactEmail.value,
 | 
			
		||||
            'MobilePhone': this.#refs.contactMobile.value,
 | 
			
		||||
            'OptOut': this.#refs.checkOpt.querySelector('input').checked,
 | 
			
		||||
            'Notes': this.#refs.contactNotes.value
 | 
			
		||||
        };
 | 
			
		||||
        const title = this.#option.contact == null ? r('addContact', 'Add Contact') : r('editContact', 'Edit Contact');
 | 
			
		||||
        if (nullOrEmpty(item.Name)) {
 | 
			
		||||
            showAlert(title, r('contactNameRequired', 'Contact Name cannot be empty.'), 'warn')
 | 
			
		||||
                .then(() => this.#refs.contactName.focus());
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (nullOrEmpty(item.Email) && nullOrEmpty(item.MobilePhone)) {
 | 
			
		||||
            showAlert(title, r('contactEmailPhoneRequired', 'Email and Mobile Phone cannot both be empty.'), 'warn')
 | 
			
		||||
                .then(() => nullOrEmpty(item.Email) ?
 | 
			
		||||
                    this.#refs.contactEmail.focus() :
 | 
			
		||||
                    this.#refs.contactMobile.focus());
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (!nullOrEmpty(item.Email) && !isEmail(item.Email)) {
 | 
			
		||||
            showAlert(title, r('contactEmailInvalid', 'The email address is invalid.'), 'warn')
 | 
			
		||||
                .then(() => this.#refs.contactEmail.focus());
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return item;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Contact;
 | 
			
		||||
@@ -7,7 +7,30 @@ import { setTooltip } from "../../ui/tooltip";
 | 
			
		||||
import { createIcon } from "../../ui/icon";
 | 
			
		||||
import { createCheckbox } from "../../ui/checkbox";
 | 
			
		||||
import { createBox } from "./lib";
 | 
			
		||||
import { createPopup } from "../../ui/popup";
 | 
			
		||||
import { createPopup, showAlert, showConfirm } from "../../ui/popup";
 | 
			
		||||
import Grid from "../../ui/grid";
 | 
			
		||||
import Contact from "./contact";
 | 
			
		||||
 | 
			
		||||
class NoteCol extends Grid.GridColumn {
 | 
			
		||||
    static create() {
 | 
			
		||||
        const wrapper = createElement('div', div => {
 | 
			
		||||
            div.style.width = '100px';
 | 
			
		||||
        },
 | 
			
		||||
            createElement('div', 'contact-name'),
 | 
			
		||||
            createElement('div', 'contact-note')
 | 
			
		||||
        );
 | 
			
		||||
        return wrapper;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static setValue(element, _val, item) {
 | 
			
		||||
        const name = element.querySelector('.contact-name');
 | 
			
		||||
        name.innerText = item.Name;
 | 
			
		||||
        if (name.scrollWidth > name.offsetWidth) {
 | 
			
		||||
            setTooltip(name, item.Name);
 | 
			
		||||
        }
 | 
			
		||||
        element.querySelector('.contact-note').innerText = item.Notes;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class CustomerCommunication {
 | 
			
		||||
    #container;
 | 
			
		||||
@@ -16,6 +39,8 @@ class CustomerCommunication {
 | 
			
		||||
    #followers;
 | 
			
		||||
    #enter;
 | 
			
		||||
    #message;
 | 
			
		||||
    #data = {};
 | 
			
		||||
    #gridContact;
 | 
			
		||||
 | 
			
		||||
    constructor(opt) {
 | 
			
		||||
        this.#option = opt ?? {};
 | 
			
		||||
@@ -87,13 +112,14 @@ class CustomerCommunication {
 | 
			
		||||
        }
 | 
			
		||||
        const mp = String(c.MobilePhone).trim();
 | 
			
		||||
        const email = String(c.Email).trim();
 | 
			
		||||
        if (c.ContactPreference === '0' && !isPhone(mp) ||
 | 
			
		||||
            c.ContactPreference === '1' && !isEmail(email)) {
 | 
			
		||||
        const pref = String(c.ContactPreference);
 | 
			
		||||
        if (pref === '0' && !isPhone(mp) ||
 | 
			
		||||
            pref === '1' && !isEmail(email)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        const to = c.ContactPreference === '0' ? mp : email;
 | 
			
		||||
        const to = pref === '0' ? mp : email;
 | 
			
		||||
        return createElement('div', 'contact-item',
 | 
			
		||||
            createIcon('fa-light', c.ContactPreference === '0' ? 'comment-lines' : 'envelope'),
 | 
			
		||||
            createIcon('fa-light', pref === '0' ? 'comment-lines' : 'envelope'),
 | 
			
		||||
            setTooltip(createElement('span', span => {
 | 
			
		||||
                span.dataset.to = to;
 | 
			
		||||
                span.dataset.name = c.Name;
 | 
			
		||||
@@ -156,11 +182,16 @@ class CustomerCommunication {
 | 
			
		||||
        this.#message.scrollTop = this.#message.scrollHeight
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setData(key, data) {
 | 
			
		||||
        this.#data[key] = data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    create() {
 | 
			
		||||
        const option = this.#option;
 | 
			
		||||
        // functions
 | 
			
		||||
        const checkAutoUpdate = createCheckbox({
 | 
			
		||||
            className: 'check-auto-update',
 | 
			
		||||
            checked: this.#option.autoUpdates,
 | 
			
		||||
            checked: option.autoUpdates,
 | 
			
		||||
            checkedNode: createIcon('fa-regular', 'redo-alt'),
 | 
			
		||||
            uncheckedNode: createIcon('fa-regular', 'ban'),
 | 
			
		||||
            onchange: function () {
 | 
			
		||||
@@ -171,7 +202,7 @@ class CustomerCommunication {
 | 
			
		||||
        });
 | 
			
		||||
        const checkLink = createCheckbox({
 | 
			
		||||
            className: 'check-status-link',
 | 
			
		||||
            checked: this.#option.statusLink,
 | 
			
		||||
            checked: option.statusLink,
 | 
			
		||||
            checkedNode: createIcon('fa-regular', 'link'),
 | 
			
		||||
            uncheckedNode: createIcon('fa-regular', 'unlink'),
 | 
			
		||||
            onchange: function () {
 | 
			
		||||
@@ -185,10 +216,10 @@ class CustomerCommunication {
 | 
			
		||||
                createElement('div', div => div.innerText = r('messages', 'Customer Communication')),
 | 
			
		||||
                createElement('div', div => {
 | 
			
		||||
                    div.className = 'title-company';
 | 
			
		||||
                    if (nullOrEmpty(this.#option.companyName)) {
 | 
			
		||||
                    if (nullOrEmpty(option.companyName)) {
 | 
			
		||||
                        div.style.display = 'none';
 | 
			
		||||
                    } else {
 | 
			
		||||
                        div.innerText = this.#option.companyName;
 | 
			
		||||
                        div.innerText = option.companyName;
 | 
			
		||||
                    }
 | 
			
		||||
                })
 | 
			
		||||
            ),
 | 
			
		||||
@@ -198,7 +229,7 @@ class CustomerCommunication {
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
        // contacts
 | 
			
		||||
        const readonly = this.#option.readonly;
 | 
			
		||||
        const readonly = option.readonly;
 | 
			
		||||
        const contacts = createElement('div');
 | 
			
		||||
        container.append(
 | 
			
		||||
            createElement('div', 'contact-bar',
 | 
			
		||||
@@ -218,30 +249,236 @@ class CustomerCommunication {
 | 
			
		||||
                        button.appendChild(createIcon('fa-solid', 'user-edit'));
 | 
			
		||||
                        setTooltip(button, r('editContacts', 'Edit Contacts'));
 | 
			
		||||
                        button.addEventListener('click', () => {
 | 
			
		||||
                            // TODO:
 | 
			
		||||
                            const pop = createPopup(
 | 
			
		||||
                                createElement('div', div => {
 | 
			
		||||
                                    div.style.display = 'flex';
 | 
			
		||||
                                    div.append(
 | 
			
		||||
                                        createElement('span', span => {
 | 
			
		||||
                                            span.style.flex = '1 1 auto';
 | 
			
		||||
                                            span.innerText = r('editContacts', 'Edit Contacts');
 | 
			
		||||
                                        }),
 | 
			
		||||
                                        createElement('div', div => {
 | 
			
		||||
                                            div.className = 'popup-move';
 | 
			
		||||
                                            div.style.flex = '1 1 auto';
 | 
			
		||||
                                        },
 | 
			
		||||
                                            createElement('div', div => div.innerText = r('editContacts', 'Edit Contacts')),
 | 
			
		||||
                                            createElement('div', div => {
 | 
			
		||||
                                                div.className = 'title-company';
 | 
			
		||||
                                                if (nullOrEmpty(option.companyName)) {
 | 
			
		||||
                                                    div.style.display = 'none';
 | 
			
		||||
                                                } else {
 | 
			
		||||
                                                    div.innerText = option.companyName;
 | 
			
		||||
                                                }
 | 
			
		||||
                                            })
 | 
			
		||||
                                        ),
 | 
			
		||||
                                        createElement('button', button => {
 | 
			
		||||
                                            button.style.flex = '0 0 auto';
 | 
			
		||||
                                            button.style.backgroundColor = 'rgb(1, 199, 172)';
 | 
			
		||||
                                            button.style.marginRight = '10px';
 | 
			
		||||
                                            button.className = 'roundbtn button-add-contact';
 | 
			
		||||
                                            button.backgroundColor = 'rgb(1, 199, 172)';
 | 
			
		||||
                                            button.appendChild(createIcon('fa-regular', 'user'));
 | 
			
		||||
                                            button.appendChild(createIcon('fa-solid', 'user-plus', {
 | 
			
		||||
                                                width: '16px',
 | 
			
		||||
                                                height: '16px'
 | 
			
		||||
                                            }));
 | 
			
		||||
                                            button.addEventListener('click', () => {
 | 
			
		||||
                                                const add = new Contact({
 | 
			
		||||
                                                    onSave: (item) => {
 | 
			
		||||
                                                        const exists = this.#gridContact.source.some(s => s.Name === item.Name && s.MobilePhone === item.MobilePhone);
 | 
			
		||||
                                                        if (exists) {
 | 
			
		||||
                                                            showAlert(r('addContact', 'Add Contact'), r('contactUniqueRequired', 'Contact name and contact mobile must be a unique combination.'), 'warn');
 | 
			
		||||
                                                            return false;
 | 
			
		||||
                                                        }
 | 
			
		||||
                                                        if (typeof option.onSave === 'function') {
 | 
			
		||||
                                                            const result = option.onSave(item, true);
 | 
			
		||||
                                                            if (result !== false) {
 | 
			
		||||
                                                                this.#gridContact.reload();
 | 
			
		||||
                                                            }
 | 
			
		||||
                                                            return result;
 | 
			
		||||
                                                        }
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                });
 | 
			
		||||
                                                add.show(container);
 | 
			
		||||
                                            });
 | 
			
		||||
                                            setTooltip(button, r('addContact', 'Add Contact'))
 | 
			
		||||
                                        })
 | 
			
		||||
                                    )
 | 
			
		||||
                                }),
 | 
			
		||||
                                createElement('div', div => {
 | 
			
		||||
                                    div.style.height = '300px';
 | 
			
		||||
                                    div.innerText = 'Contacts from Customer Record';
 | 
			
		||||
                                }));
 | 
			
		||||
                            container.append(pop);
 | 
			
		||||
                            setTimeout(() => pop.style.opacity = 1, 0);
 | 
			
		||||
                                createElement('div', null,
 | 
			
		||||
                                    createElement('div', div => {
 | 
			
		||||
                                        div.style.fontWeight = 'bold';
 | 
			
		||||
                                        div.innerText = r('contactFromRecord', 'Contacts from Customer Record');
 | 
			
		||||
                                    }),
 | 
			
		||||
                                    createElement('div', div => {
 | 
			
		||||
                                        div.className = 'contacts-record';
 | 
			
		||||
                                        div.style.maxHeight = '400px';
 | 
			
		||||
                                        div.style.width = '660px';
 | 
			
		||||
                                    }),
 | 
			
		||||
                                    createElement('div', div => {
 | 
			
		||||
                                        div.style.fontWeight = 'bold';
 | 
			
		||||
                                        div.innerText = r('contactFromWorkOrder', 'Contacts not on Customer Record');
 | 
			
		||||
                                    }),
 | 
			
		||||
                                    createElement('div', div => {
 | 
			
		||||
                                        div.className = 'contacts-wo';
 | 
			
		||||
                                        div.style.maxHeight = '200px';
 | 
			
		||||
                                        div.style.width = '660px';
 | 
			
		||||
                                    })
 | 
			
		||||
                                )
 | 
			
		||||
                            );
 | 
			
		||||
                            pop.show(container).then(() => {
 | 
			
		||||
                                const selectedCol = {
 | 
			
		||||
                                    key: 'selected',
 | 
			
		||||
                                    type: Grid.ColumnTypes.Checkbox,
 | 
			
		||||
                                    width: 50,
 | 
			
		||||
                                    enabled: item => !item.OptOut && !item.OptOut_BC
 | 
			
		||||
                                };
 | 
			
		||||
                                const iconCol = {
 | 
			
		||||
                                    key: 'type',
 | 
			
		||||
                                    type: Grid.ColumnTypes.Icon,
 | 
			
		||||
                                    width: 50,
 | 
			
		||||
                                    filter: c => String(c.ContactPreference) === '0' ? 'comment-lines' : 'envelope',
 | 
			
		||||
                                    className: 'icon-contact-type',
 | 
			
		||||
                                    iconType: 'fa-light'
 | 
			
		||||
                                };
 | 
			
		||||
                                const nameCol = { key: 'Name', type: NoteCol, width: 160 };
 | 
			
		||||
                                const buttonCol = {
 | 
			
		||||
                                    type: Grid.ColumnTypes.Icon,
 | 
			
		||||
                                    width: 40,
 | 
			
		||||
                                    align: 'center',
 | 
			
		||||
                                    iconType: 'fa-light'
 | 
			
		||||
                                };
 | 
			
		||||
                                const createEditCol = grid => {
 | 
			
		||||
                                    return {
 | 
			
		||||
                                        key: 'edit',
 | 
			
		||||
                                        ...buttonCol,
 | 
			
		||||
                                        text: 'edit',
 | 
			
		||||
                                        tooltip: r('edit', 'Edit'),
 | 
			
		||||
                                        events: {
 | 
			
		||||
                                            onclick: function () {
 | 
			
		||||
                                                const edit = new Contact({
 | 
			
		||||
                                                    contact: this,
 | 
			
		||||
                                                    onSave: item => {
 | 
			
		||||
                                                        const exists = grid.source.some(s => s !== this && s.Name === item.Name && s.MobilePhone === item.MobilePhone);
 | 
			
		||||
                                                        if (exists) {
 | 
			
		||||
                                                            showAlert(r('editContact', 'Edit Contact'), r('contactUniqueRequired', 'Contact name and contact mobile must be a unique combination.'), 'warn');
 | 
			
		||||
                                                            return false;
 | 
			
		||||
                                                        }
 | 
			
		||||
                                                        if (typeof option.onSave === 'function') {
 | 
			
		||||
                                                            const result = option.onSave(item);
 | 
			
		||||
                                                            if (result !== false) {
 | 
			
		||||
                                                                grid.refresh();
 | 
			
		||||
                                                            }
 | 
			
		||||
                                                            return result;
 | 
			
		||||
                                                        }
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                });
 | 
			
		||||
                                                edit.show(container);
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                };
 | 
			
		||||
                                // contacts from customer record
 | 
			
		||||
                                const grid = new Grid();
 | 
			
		||||
                                grid.height = 0;
 | 
			
		||||
                                grid.allowHtml = true;
 | 
			
		||||
                                grid.headerVisible = false;
 | 
			
		||||
                                grid.columns = [
 | 
			
		||||
                                    selectedCol,
 | 
			
		||||
                                    iconCol,
 | 
			
		||||
                                    nameCol,
 | 
			
		||||
                                    { key: 'Email', width: 180 },
 | 
			
		||||
                                    { key: 'MobilePhone', width: 130 },
 | 
			
		||||
                                    createEditCol(grid),
 | 
			
		||||
                                    {
 | 
			
		||||
                                        key: 'delete',
 | 
			
		||||
                                        ...buttonCol,
 | 
			
		||||
                                        text: 'times',
 | 
			
		||||
                                        tooltip: r('delete', 'Delete'),
 | 
			
		||||
                                        events: {
 | 
			
		||||
                                            onclick: function () {
 | 
			
		||||
                                                showConfirm(r('remoteContact', 'Remove Contact'), r('removeFromCustomer', 'You are removing {name} from customer record.\n\nDo you want to Continue?').replace('{name}', this.Name), [
 | 
			
		||||
                                                    { key: 'continue', text: r('continue', 'Continue') },
 | 
			
		||||
                                                    { key: 'cancel', text: r('cancel', 'Cancel') }
 | 
			
		||||
                                                ]).then(result => {
 | 
			
		||||
                                                    if (result === 'continue') {
 | 
			
		||||
                                                        if (typeof option.onDelete === 'function') {
 | 
			
		||||
                                                            option.onDelete(result, this, true);
 | 
			
		||||
                                                        }
 | 
			
		||||
                                                        const index = grid.source.indexOf(this);
 | 
			
		||||
                                                        if (index >= 0) {
 | 
			
		||||
                                                            const source = grid.source;
 | 
			
		||||
                                                            source.splice(index, 1);
 | 
			
		||||
                                                            grid.extraRows = source.filter(c => !nullOrEmpty(c.Notes)).length;
 | 
			
		||||
                                                            grid.source = source;
 | 
			
		||||
                                                        }
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                });
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                ];
 | 
			
		||||
                                grid.init(pop.container.querySelector('.contacts-record'));
 | 
			
		||||
                                const customerRecords = this.#data.contacts.filter(c => c.Id >= 0).map(c => {
 | 
			
		||||
                                    if (c.OptOut || c.OptOut_BC) {
 | 
			
		||||
                                        return c;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (typeof c.selected === 'undefined') {
 | 
			
		||||
                                        c.selected = true;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    return c;
 | 
			
		||||
                                });
 | 
			
		||||
                                grid.extraRows = customerRecords.filter(c => !nullOrEmpty(c.Notes)).length;
 | 
			
		||||
                                grid.source = customerRecords;
 | 
			
		||||
                                this.#gridContact = grid;
 | 
			
		||||
 | 
			
		||||
                                // contacts from work order only
 | 
			
		||||
                                const gridWo = new Grid();
 | 
			
		||||
                                gridWo.height = 0;
 | 
			
		||||
                                gridWo.allowHtml = true;
 | 
			
		||||
                                gridWo.headerVisible = false;
 | 
			
		||||
                                gridWo.columns = [
 | 
			
		||||
                                    selectedCol,
 | 
			
		||||
                                    iconCol,
 | 
			
		||||
                                    nameCol,
 | 
			
		||||
                                    { key: 'Email', width: 180 },
 | 
			
		||||
                                    { key: 'MobilePhone', width: 130 },
 | 
			
		||||
                                    createEditCol(gridWo),
 | 
			
		||||
                                    {
 | 
			
		||||
                                        key: 'delete',
 | 
			
		||||
                                        ...buttonCol,
 | 
			
		||||
                                        text: 'times',
 | 
			
		||||
                                        tooltip: r('delete', 'Delete'),
 | 
			
		||||
                                        events: {
 | 
			
		||||
                                            onclick: () => {
 | 
			
		||||
                                                showConfirm(r('remoteContact', 'Remove Contact'), r('removeFromWorkorder', 'You are removing {name} from work order.\n\nDo you want to Continue?').replace('{name}', this.Name), [
 | 
			
		||||
                                                    { key: 'continue', text: r('continue', 'Continue') },
 | 
			
		||||
                                                    { key: 'cancel', text: r('cancel', 'Cancel') }
 | 
			
		||||
                                                ]).then(result => {
 | 
			
		||||
                                                    if (result === 'continue') {
 | 
			
		||||
                                                        if (typeof option.onDelete === 'function') {
 | 
			
		||||
                                                            option.onDelete(result, this);
 | 
			
		||||
                                                        }
 | 
			
		||||
                                                        const index = gridWo.source.indexOf(this);
 | 
			
		||||
                                                        if (index >= 0) {
 | 
			
		||||
                                                            const source = gridWo.source;
 | 
			
		||||
                                                            source.splice(index, 1);
 | 
			
		||||
                                                            gridWo.extraRows = source.filter(c => !nullOrEmpty(c.Notes)).length;
 | 
			
		||||
                                                            gridWo.source = source;
 | 
			
		||||
                                                        }
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                });
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                ];
 | 
			
		||||
                                gridWo.init(pop.container.querySelector('.contacts-wo'));
 | 
			
		||||
                                const workOrderOnly = this.#data.contacts.filter(c => c.Id < 0).map(c => {
 | 
			
		||||
                                    if (c.OptOut || c.OptOut_BC) {
 | 
			
		||||
                                        return c;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    if (typeof c.selected === 'undefined') {
 | 
			
		||||
                                        c.selected = true;
 | 
			
		||||
                                    }
 | 
			
		||||
                                    return c;
 | 
			
		||||
                                });
 | 
			
		||||
                                gridWo.extraRows = workOrderOnly.filter(c => !nullOrEmpty(c.Notes)).length;
 | 
			
		||||
                                gridWo.source = workOrderOnly;
 | 
			
		||||
                            });
 | 
			
		||||
                        });
 | 
			
		||||
                    })
 | 
			
		||||
                )
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,4 @@
 | 
			
		||||
:root {
 | 
			
		||||
    --title-color: #fff;
 | 
			
		||||
    --title-bg-color: rgb(68, 114, 196);
 | 
			
		||||
}
 | 
			
		||||
@import '../../../css/variables/definition.scss';
 | 
			
		||||
 | 
			
		||||
.comm {
 | 
			
		||||
    display: flex;
 | 
			
		||||
@@ -318,4 +315,34 @@
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .popup-mask .grid {
 | 
			
		||||
        height: 100%;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
 | 
			
		||||
        >.grid-body .grid-body-content>.grid-row>td {
 | 
			
		||||
            vertical-align: top;
 | 
			
		||||
 | 
			
		||||
            .icon-contact-type {
 | 
			
		||||
                cursor: unset;
 | 
			
		||||
 | 
			
		||||
                >svg {
 | 
			
		||||
                    fill: #333;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                &:hover>svg {
 | 
			
		||||
                    opacity: unset;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .contact-name {
 | 
			
		||||
                overflow: hidden;
 | 
			
		||||
                text-overflow: ellipsis;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .contact-note {
 | 
			
		||||
                color: #999;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,6 +4,7 @@ import { createCheckbox, resolveCheckbox } from "./ui/checkbox";
 | 
			
		||||
import { setTooltip, resolveTooltip } from "./ui/tooltip";
 | 
			
		||||
import Dropdown from "./ui/dropdown";
 | 
			
		||||
import Grid from "./ui/grid";
 | 
			
		||||
import Popup from "./ui/popup";
 | 
			
		||||
import { createPopup } from "./ui/popup";
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
@@ -21,5 +22,6 @@ export {
 | 
			
		||||
  // grid
 | 
			
		||||
  Grid,
 | 
			
		||||
  // popup
 | 
			
		||||
  Popup,
 | 
			
		||||
  createPopup
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								lib/ui/grid.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								lib/ui/grid.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -33,7 +33,7 @@ interface GridColumnDefinition {
 | 
			
		||||
    caption?: string;
 | 
			
		||||
    width?: Number;
 | 
			
		||||
    align?: "left" | "center" | "right";
 | 
			
		||||
    enabled?: boolean | string;
 | 
			
		||||
    enabled?: boolean | string | ((item: GridItem | any) => boolean);
 | 
			
		||||
    css?: { [key: string]: string };
 | 
			
		||||
    styleFilter?: (item: GridItem | any) => { [key: string]: string };
 | 
			
		||||
    textStyle?: { [key: string]: string };
 | 
			
		||||
@@ -51,6 +51,7 @@ interface GridColumnDefinition {
 | 
			
		||||
    dropOptions?: DropdownOptions;
 | 
			
		||||
    source?: Array<any> | ((item: GridItem | any) => Array<any> | Promise<Array<GridSourceItem>>);
 | 
			
		||||
    iconType?: string;
 | 
			
		||||
    className?: string | ((item: GridItem | any) => string);
 | 
			
		||||
    text?: string;
 | 
			
		||||
    tooltip?: string;
 | 
			
		||||
    onallchecked?: (this: Grid, col: GridColumnDefinition, flag: boolean) => void;
 | 
			
		||||
@@ -73,6 +74,7 @@ interface Grid {
 | 
			
		||||
    langs?: { all: string, ok: string, reset: string };
 | 
			
		||||
    virtualCount?: Number;
 | 
			
		||||
    rowHeight?: Number;
 | 
			
		||||
    extraRows?: Number;
 | 
			
		||||
    filterRowHeight?: Number;
 | 
			
		||||
    height?: Number;
 | 
			
		||||
    readonly?: boolean;
 | 
			
		||||
@@ -80,6 +82,7 @@ interface Grid {
 | 
			
		||||
    fullrowClick?: boolean;
 | 
			
		||||
    allowHtml?: boolean;
 | 
			
		||||
    holderDisabled?: boolean;
 | 
			
		||||
    headerVisible?: boolean;
 | 
			
		||||
    window?: Window
 | 
			
		||||
    sortIndex?: Number;
 | 
			
		||||
    sortDirection?: keyof GridColumnDirection;
 | 
			
		||||
 
 | 
			
		||||
@@ -172,19 +172,28 @@ class GridCheckboxColumn extends GridColumn {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class GridIconColumn extends GridColumn {
 | 
			
		||||
    static create() { return createElement('span', 'icon') }
 | 
			
		||||
    static create() { return createElement('span', 'col-icon') }
 | 
			
		||||
 | 
			
		||||
    static setValue(element, val, item, col) {
 | 
			
		||||
        let className = col.className;
 | 
			
		||||
        if (typeof className === 'function') {
 | 
			
		||||
            className = className.call(col, item);
 | 
			
		||||
        }
 | 
			
		||||
        if (className == null) {
 | 
			
		||||
            element.className = 'col-icon';
 | 
			
		||||
        } else {
 | 
			
		||||
            element.className = `col-icon ${className}`;
 | 
			
		||||
        }
 | 
			
		||||
        let type = col.iconType;
 | 
			
		||||
        if (typeof type === 'function') {
 | 
			
		||||
            type = type(item);
 | 
			
		||||
            type = type.call(col, item);
 | 
			
		||||
        }
 | 
			
		||||
        type ??= 'fa-regular';
 | 
			
		||||
        if (element.dataset.type !== type || element.dataset.icon !== val) {
 | 
			
		||||
            const icon = createIcon(type, val);
 | 
			
		||||
            // const layer = element.children[0];
 | 
			
		||||
            element.replaceChildren(icon);
 | 
			
		||||
            !nullOrEmpty(col.tooltip) && setTooltip(icon, col.tooltip);
 | 
			
		||||
            !nullOrEmpty(col.tooltip) && setTooltip(element, col.tooltip);
 | 
			
		||||
            element.dataset.type = type;
 | 
			
		||||
            element.dataset.icon = val;
 | 
			
		||||
        }
 | 
			
		||||
@@ -238,7 +247,8 @@ class Grid {
 | 
			
		||||
        reset: r('reset', 'Reset')
 | 
			
		||||
    };
 | 
			
		||||
    virtualCount = 100;
 | 
			
		||||
    rowHeight = 39;
 | 
			
		||||
    rowHeight = 36;
 | 
			
		||||
    extraRows = 0;
 | 
			
		||||
    filterRowHeight = 30;
 | 
			
		||||
    height;
 | 
			
		||||
    readonly;
 | 
			
		||||
@@ -246,6 +256,7 @@ class Grid {
 | 
			
		||||
    fullrowClick = true;
 | 
			
		||||
    allowHtml = false;
 | 
			
		||||
    holderDisabled = false;
 | 
			
		||||
    headerVisible = true;
 | 
			
		||||
    window = global;
 | 
			
		||||
    sortIndex = -1;
 | 
			
		||||
    sortDirection = 1;
 | 
			
		||||
@@ -416,7 +427,7 @@ class Grid {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    scrollToIndex(index) {
 | 
			
		||||
        const top = this.#scrollToTop(index * this.rowHeight, true);
 | 
			
		||||
        const top = this.#scrollToTop(index * (this.rowHeight + 1), true);
 | 
			
		||||
        this.#refs.body.scrollTop = top;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -431,13 +442,15 @@ class Grid {
 | 
			
		||||
        //     body.style.top = `${height}px`;
 | 
			
		||||
        //     top = height;
 | 
			
		||||
        // }
 | 
			
		||||
        const top = this.#refs.header.offsetHeight;
 | 
			
		||||
        const top = this.headerVisible === false ? 0 : this.#refs.header.offsetHeight;
 | 
			
		||||
 | 
			
		||||
        let height = this.height;
 | 
			
		||||
        if (isNaN(height) || height <= 0) {
 | 
			
		||||
        if (height === 0) {
 | 
			
		||||
            height = this.#containerHeight;
 | 
			
		||||
        } else if (isNaN(height) || height < 0) {
 | 
			
		||||
            height = this.#el.offsetHeight - top;
 | 
			
		||||
        }
 | 
			
		||||
        const count = truncate((height - 1) / this.rowHeight) * (RedumCount * 2) + 1;
 | 
			
		||||
        const count = truncate((height - 1) / (this.rowHeight + 1)) * (RedumCount * 2) + 1;
 | 
			
		||||
        if (force || count !== this.#rowCount) {
 | 
			
		||||
            this.#rowCount = count;
 | 
			
		||||
            this.reload();
 | 
			
		||||
@@ -446,7 +459,11 @@ class Grid {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    reload() {
 | 
			
		||||
        this.#containerHeight = this.#currentSource.length * this.rowHeight;
 | 
			
		||||
        let length = this.#currentSource.length;
 | 
			
		||||
        if (this.extraRows > 0) {
 | 
			
		||||
            length += this.extraRows;
 | 
			
		||||
        }
 | 
			
		||||
        this.#containerHeight = length * (this.rowHeight + 1);
 | 
			
		||||
        this.#refs.body.scrollTop = 0;
 | 
			
		||||
        this.#refs.body.scrollLeft = 0;
 | 
			
		||||
        this.#refs.bodyContent.style.top = '0px';
 | 
			
		||||
@@ -555,6 +572,9 @@ class Grid {
 | 
			
		||||
 | 
			
		||||
    #createHeader() {
 | 
			
		||||
        const thead = createElement('table', 'grid-header');
 | 
			
		||||
        if (this.headerVisible === false) {
 | 
			
		||||
            thead.style.display = 'none';
 | 
			
		||||
        }
 | 
			
		||||
        const header = createElement('tr');
 | 
			
		||||
        thead.appendChild(header);
 | 
			
		||||
        const sizer = this.#refs.sizer;
 | 
			
		||||
@@ -838,7 +858,9 @@ class Grid {
 | 
			
		||||
                    enabled = false;
 | 
			
		||||
                } else {
 | 
			
		||||
                    enabled = col.enabled;
 | 
			
		||||
                    if (typeof enabled === 'string') {
 | 
			
		||||
                    if (typeof enabled === 'function') {
 | 
			
		||||
                        enabled = enabled.call(col, item);
 | 
			
		||||
                    } else if (typeof enabled === 'string') {
 | 
			
		||||
                        enabled = item[enabled];
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@@ -1023,7 +1045,7 @@ class Grid {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #scrollToTop(top, reload) {
 | 
			
		||||
        const rowHeight = this.rowHeight;
 | 
			
		||||
        const rowHeight = (this.rowHeight + 1);
 | 
			
		||||
        top -= (top % (rowHeight * 2)) + (RedumCount * rowHeight);
 | 
			
		||||
        if (top < 0) {
 | 
			
		||||
            top = 0;
 | 
			
		||||
@@ -1234,7 +1256,8 @@ class Grid {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const key = col.key;
 | 
			
		||||
        const test = typeof col.enabled === 'string';
 | 
			
		||||
        const isFunction = typeof col.enabled === 'function';
 | 
			
		||||
        const isString = typeof col.enabled === 'string';
 | 
			
		||||
        if (typeof col.onallchecked === 'function') {
 | 
			
		||||
            col.onallchecked.call(this, col, flag);
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -1243,7 +1266,7 @@ class Grid {
 | 
			
		||||
                if (item == null) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                const enabled = test ? item[col.enabled] : col.enabled;
 | 
			
		||||
                const enabled = isFunction ? col.enabled(item) : isString ? item[col.enabled] : col.enabled;
 | 
			
		||||
                if (enabled !== false) {
 | 
			
		||||
                    item[key] = flag;
 | 
			
		||||
                    row.__changed = true;
 | 
			
		||||
@@ -1413,7 +1436,12 @@ class Grid {
 | 
			
		||||
        if (item == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const enabled = typeof col.enabled === 'string' ? item[col.enabled] : col.enabled;
 | 
			
		||||
        let enabled = col.enabled;
 | 
			
		||||
        if (typeof enabled === 'function') {
 | 
			
		||||
            enabled = enabled.call(col, item);
 | 
			
		||||
        } else if (typeof enabled === 'string') {
 | 
			
		||||
            enabled = item[enabled];
 | 
			
		||||
        }
 | 
			
		||||
        if (enabled !== false) {
 | 
			
		||||
            item[col.key] = value;
 | 
			
		||||
            row.__changed = true;
 | 
			
		||||
 
 | 
			
		||||
@@ -14,9 +14,15 @@
 | 
			
		||||
 | 
			
		||||
        document.querySelector('#button-popup').addEventListener('click', () => {
 | 
			
		||||
            const popup = ui.createPopup('title', 'content',
 | 
			
		||||
                { text: 'Ok', trigger: () => true });
 | 
			
		||||
            document.body.appendChild(popup);
 | 
			
		||||
            setTimeout(() => popup.style.opacity = 1);
 | 
			
		||||
                {
 | 
			
		||||
                    text: 'Loading', trigger: p => {
 | 
			
		||||
                        p.loading = true;
 | 
			
		||||
                        setTimeout(() => p.loading = false, 1000);
 | 
			
		||||
                        return false;
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                { text: 'OK' });
 | 
			
		||||
            popup.show();
 | 
			
		||||
        });
 | 
			
		||||
    </script>
 | 
			
		||||
    <style type="text/css">
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										188
									
								
								lib/ui/popup.js
									
									
									
									
									
								
							
							
						
						
									
										188
									
								
								lib/ui/popup.js
									
									
									
									
									
								
							@@ -1,45 +1,161 @@
 | 
			
		||||
import "../../css/popup.scss";
 | 
			
		||||
import { createElement } from "../functions";
 | 
			
		||||
import { r } from "../utility";
 | 
			
		||||
import { createIcon } from "./icon";
 | 
			
		||||
 | 
			
		||||
function createPopup(title, content, ...buttons) {
 | 
			
		||||
    const mask = createElement('div', 'popup-mask');
 | 
			
		||||
    const container = createElement('div', 'popup-container');
 | 
			
		||||
    const close = () => {
 | 
			
		||||
        mask.classList.add('popup-active');
 | 
			
		||||
        mask.style.opacity = 0;
 | 
			
		||||
        setTimeout(() => mask.remove(), 120);
 | 
			
		||||
    };
 | 
			
		||||
    container.append(
 | 
			
		||||
        createElement('div', header => {
 | 
			
		||||
            header.className = 'popup-header';
 | 
			
		||||
            if (title instanceof HTMLElement) {
 | 
			
		||||
class Popup {
 | 
			
		||||
    #mask;
 | 
			
		||||
    #option;
 | 
			
		||||
 | 
			
		||||
    constructor(opts = {}) {
 | 
			
		||||
        this.#option = opts;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get container() { return this.#mask.querySelector('.popup-container') }
 | 
			
		||||
 | 
			
		||||
    create() {
 | 
			
		||||
        const mask = createElement('div', 'popup-mask');
 | 
			
		||||
        const container = createElement('div', 'popup-container');
 | 
			
		||||
        const close = () => {
 | 
			
		||||
            mask.classList.add('popup-active');
 | 
			
		||||
            mask.style.opacity = 0;
 | 
			
		||||
            setTimeout(() => mask.remove(), 120);
 | 
			
		||||
        };
 | 
			
		||||
        let content = this.#option.content;
 | 
			
		||||
        if (!(content instanceof HTMLElement)) {
 | 
			
		||||
            content = createElement('div', d => d.innerText = content);
 | 
			
		||||
        }
 | 
			
		||||
        container.append(
 | 
			
		||||
            createElement('div', header => {
 | 
			
		||||
                header.className = 'popup-header';
 | 
			
		||||
                let title = this.#option.title;
 | 
			
		||||
                if (!(title instanceof HTMLElement)) {
 | 
			
		||||
                    title = createElement('div', t => t.innerText = title);
 | 
			
		||||
                }
 | 
			
		||||
                header.appendChild(title);
 | 
			
		||||
            } else {
 | 
			
		||||
                header.appendChild(createElement('div', t => t.innerText = title));
 | 
			
		||||
            }
 | 
			
		||||
            const cancel = createIcon('fa-regular', 'times');
 | 
			
		||||
            cancel.addEventListener('click', () => close());
 | 
			
		||||
            header.appendChild(cancel);
 | 
			
		||||
        }),
 | 
			
		||||
        createElement('div', 'popup-body', content),
 | 
			
		||||
        createElement('div', 'popup-footer', ...buttons.map(b => {
 | 
			
		||||
            const button = createElement('div', 'popup-button');
 | 
			
		||||
            button.innerText = b.text;
 | 
			
		||||
            if (typeof b.trigger === 'function') {
 | 
			
		||||
                button.addEventListener('click', () => {
 | 
			
		||||
                    if (b.trigger(container) === false) {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    close();
 | 
			
		||||
                const move = title.querySelector('.popup-move') ?? title;
 | 
			
		||||
                move.addEventListener('mousedown', e => {
 | 
			
		||||
                    const x = e.clientX - container.offsetLeft;
 | 
			
		||||
                    const y = e.clientY - container.offsetTop;
 | 
			
		||||
                    const move = e => {
 | 
			
		||||
                        container.style.left = `${e.clientX - x}px`;
 | 
			
		||||
                        container.style.top = `${e.clientY - y}px`;
 | 
			
		||||
                    };
 | 
			
		||||
                    mask.addEventListener('mousemove', move, { passive: false });
 | 
			
		||||
                    mask.addEventListener('mouseup', () => {
 | 
			
		||||
                        mask.removeEventListener('mousemove', move, { passive: false });
 | 
			
		||||
                    });
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }))
 | 
			
		||||
    );
 | 
			
		||||
    mask.appendChild(container);
 | 
			
		||||
    return mask;
 | 
			
		||||
                const cancel = createIcon('fa-regular', 'times');
 | 
			
		||||
                cancel.addEventListener('click', () => close());
 | 
			
		||||
                header.appendChild(cancel);
 | 
			
		||||
            }),
 | 
			
		||||
            createElement('div', 'popup-body', content, createElement('div', 'popup-loading',
 | 
			
		||||
                createElement('div', null, createIcon('fa-regular', 'spinner-third'))
 | 
			
		||||
            ))
 | 
			
		||||
        );
 | 
			
		||||
        if (Array.isArray(this.#option.buttons)) {
 | 
			
		||||
            container.appendChild(
 | 
			
		||||
                createElement('div', 'popup-footer', ...this.#option.buttons.map(b => {
 | 
			
		||||
                    const button = createElement('div', 'popup-button');
 | 
			
		||||
                    button.innerText = b.text;
 | 
			
		||||
                    button.addEventListener('click', () => {
 | 
			
		||||
                        if (typeof b.trigger === 'function' && b.trigger(this) === false) {
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                        close();
 | 
			
		||||
                    });
 | 
			
		||||
                    return button;
 | 
			
		||||
                }))
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        mask.appendChild(container);
 | 
			
		||||
        this.#mask = mask;
 | 
			
		||||
        return mask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    show(parent = document.body) {
 | 
			
		||||
        if (parent == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        let mask = this.#mask ?? this.create();
 | 
			
		||||
        parent.appendChild(mask);
 | 
			
		||||
        return new Promise(resolve => {
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                mask.style.opacity = 1
 | 
			
		||||
                resolve();
 | 
			
		||||
            }, 0);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get loading() { return this.#mask?.querySelector('.popup-body>.popup-loading')?.style?.visibility === 'visible' }
 | 
			
		||||
    set loading(flag) {
 | 
			
		||||
        let loading = this.#mask?.querySelector('.popup-body>.popup-loading');
 | 
			
		||||
        if (loading == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (flag === false) {
 | 
			
		||||
            loading.style.visibility = 'hidden';
 | 
			
		||||
            loading.style.opacity = 0;
 | 
			
		||||
        } else {
 | 
			
		||||
            loading.style.visibility = 'visible';
 | 
			
		||||
            loading.style.opacity = 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
    createPopup
 | 
			
		||||
export default Popup;
 | 
			
		||||
 | 
			
		||||
export function createPopup(title, content, ...buttons) {
 | 
			
		||||
    const popup = new Popup({
 | 
			
		||||
        title,
 | 
			
		||||
        content,
 | 
			
		||||
        buttons
 | 
			
		||||
    });
 | 
			
		||||
    return popup;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const iconTypes = {
 | 
			
		||||
    'info': 'info-circle',
 | 
			
		||||
    'information': 'info-circle',
 | 
			
		||||
    'warn': 'exclamation-triangle',
 | 
			
		||||
    'warning': 'exclamation-triangle',
 | 
			
		||||
    'question': 'question-circle',
 | 
			
		||||
    'error': 'times-circle'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function showAlert(title, message, iconType = 'info', parent = document.body) {
 | 
			
		||||
    return new Promise(resolve => {
 | 
			
		||||
        const popup = new Popup({
 | 
			
		||||
            title,
 | 
			
		||||
            content: createElement('div', 'message-wrapper',
 | 
			
		||||
                createIcon('fa-solid', iconTypes[iconType] ?? 'info-circle'),
 | 
			
		||||
                createElement('span', span => span.innerText = message)
 | 
			
		||||
            ),
 | 
			
		||||
            buttons: [
 | 
			
		||||
                { text: r('ok', 'OK'), trigger: resolve }
 | 
			
		||||
            ]
 | 
			
		||||
        });
 | 
			
		||||
        popup.show(parent);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function showConfirm(title, message, buttons, iconType = 'question', parent = document.body) {
 | 
			
		||||
    return new Promise(resolve => {
 | 
			
		||||
        const popup = new Popup({
 | 
			
		||||
            title,
 | 
			
		||||
            content: createElement('div', 'message-wrapper',
 | 
			
		||||
                createIcon('fa-solid', iconTypes[iconType] ?? 'question-circle'),
 | 
			
		||||
                createElement('span', span => span.innerText = message)
 | 
			
		||||
            ),
 | 
			
		||||
            buttons: buttons?.map(b => {
 | 
			
		||||
                return { text: b.text, trigger: p => resolve(b.key, p) }
 | 
			
		||||
            }) ??
 | 
			
		||||
                [
 | 
			
		||||
                    { text: r('yes', 'Yes'), trigger: p => resolve('yes', p) },
 | 
			
		||||
                    { text: r('no', 'No'), trigger: p => resolve('no', p) }
 | 
			
		||||
                ]
 | 
			
		||||
        });
 | 
			
		||||
        popup.show(parent);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
@@ -45,6 +45,7 @@ const libraries = [
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      sourcemap: true,
 | 
			
		||||
      outDir: '../../../../IronIntel/Contractor2.0/Contractor/Site/js/lib',
 | 
			
		||||
      emptyOutDir: false
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user