Subscriptions 70% done

This commit is contained in:
Ali Arya 2023-01-13 18:46:26 +03:30
parent 2fee1d2ae1
commit 22457bcd79
39 changed files with 2671 additions and 445 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
.DS_Store
node_modules
/dist
/TEMP
# local env files
.env.local

View File

@ -3,7 +3,7 @@
<template #button-content>
<div class="d-sm-flex d-none user-nav">
<p class="user-name font-weight-bolder mb-0">
{{ `${userData.first_name} ${userData.last_name}` }}
{{ `${userData.first_name} ${userData.last_name}` }}
</p>
<span class="user-status">{{ userData.role }}</span>
</div>
@ -71,7 +71,7 @@
<span>Chat</span>
</b-dropdown-item> -->
<b-dropdown-divider />
<!-- <b-dropdown-divider /> -->
<!-- <b-dropdown-item
:to="{ name: 'pages-account-setting' }"
@ -119,7 +119,6 @@ import { initialAbility } from '@/libs/acl/config'
import useJwt from '@/auth/jwt/useJwt'
import { avatarText } from '@core/utils/filter'
import { getUserData, logout } from '@/auth/utils'
import { Log } from '@/plugins/core'
export default {
components: {

View File

@ -75,6 +75,17 @@ export const url = extend('url', {
message: 'URL is invalid',
})
export const endDate = extend('end-date', {
params: ['startDate'],
validate(value, { target }) {
console.log('validate value', value)
console.log('validate target', target)
// const StartDate = new Date(value)
return true
},
message: '{_field_} must be before {_target_}.'
})
//==============================================================================
// Localization
//==============================================================================

View File

@ -1,3 +1,4 @@
// ================================================================================================
// ? TIP: It is recommended to use this file for overriding bootstrap variables.
// ================================================================================================
// ============================================================================
// ? TIP: It is recommended to use this file for overriding bootstrap
// variables.
// ============================================================================

View File

@ -0,0 +1,61 @@
<!--
CONFIRM MODAL
A MODAL THAT ALLOWS THE USER TO CONFIRM OR CANCEL AN OPERATION.
HOW TO USE:
PUT THE COMPONENT IN THE TEMPLATE SECTION, AND THEN USE VB-MODAL BOOTSTRAP-VUE
COMPONENT AND PASS IT modal-error ARGUMENT TO OPEN THE MODAL.
THE COMPONENT IS GLOBALLY IMPORTED.
EXAMPLE:
<b-button v-b-modal:modal-error>Add New User</b-button>
<ConfirmModal />
-->
<template>
<b-modal
id="confirm-modal"
v-bind:centered="true"
size="xs"
v-bind:title="title"
ok-title="Delete"
ok-variant="danger"
v-on:cancel="OnConfirm('cancel')"
v-on:ok="OnConfirm('ok')"
>
<div class="d-flex flex-column justify-content-between align-items-center" style="row-gap: 1rem;">
<h5 v-if="message !== ''" class="text-center">{{ message }}</h5>
</div>
</b-modal>
</template>
<script>
export default {
name: 'ConfirmModal',
props: {
message: {
type: String,
required: false,
default: '',
},
title: {
type: String,
required: false,
default: 'Confirm',
},
},
methods: {
OnConfirm(Value)
{
this.$emit('confirm', Value)
},
},
}
</script>
<style scoped lang="scss">
</style>

View File

@ -1,8 +1,8 @@
<!--
Documentation:
ERROR MODAL
Description:
A modal that indicates some operation has been failed
A modal that indicates some operation has been failed.
How to use:
First, import the component, put it in the template section, and then use
@ -10,7 +10,7 @@ VBModal bootstrap-vue component and pass it modal-error argument to open the
modal.
Example:
<b-button v-b-modal:modal-error>Add New User</b-button>
<b-button v-b-modal:error-modal>Add New User</b-button>
Props:
Name Type Description
@ -21,7 +21,7 @@ message String A message to be displayed to the user
<template>
<b-modal
id="modal-error"
id="error-modal"
v-bind:centered="true"
size="xs"
ok-only
@ -33,14 +33,14 @@ message String A message to be displayed to the user
<polyline style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;" points="16,34 25,25 34,16"/>
<polyline style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-miterlimit:10;" points="16,16 25,25 34,34"/><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g>
</svg>
<h5 v-if="message !== ''">{{ message }}</h5>
<h5 v-if="message !== ''" class="text-center">{{ message }}</h5>
</div>
</b-modal>
</template>
<script>
export default {
name: 'AddUserModalComponent',
name: 'ErrorModal',
props: {
message: {

View File

@ -1,12 +1,12 @@
<!--
Documentation:
SUCCESS MODAL
Description:
A modal that indicates some operation has been successful
How to use:
First, import the component, put it in the template section, and then use
VBModal bootstrap-vue component and pass it modal-success argument to open the
VBModal bootstrap-vue component and pass it success-modal argument to open the
modal.
Example:
@ -21,7 +21,7 @@ message String A message to be displayed to the user
<template>
<b-modal
id="modal-success"
id="success-modal"
v-bind:centered="true"
size="xs"
ok-only
@ -32,14 +32,14 @@ message String A message to be displayed to the user
<circle style="fill:#25ae88;" cx="25" cy="25" r="25"/>
<polyline style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;" points="38,15 22,33 12,25 "/><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g>
</svg>
<h5 v-if="message !== ''">{{ message }}</h5>
<h5 v-if="message !== ''" class="text-center">{{ message }}</h5>
</div>
</b-modal>
</template>
<script>
export default {
name: 'AddUserModalComponent',
name: 'SuccessModal',
props: {
message: {

View File

@ -45,8 +45,8 @@ table-ref ? a reference to the table engine which must be refershed e.g
<script>
import { BFormInput } from 'bootstrap-vue'
import SuccessModal from '@/components/ui/SuccessModal.vue'
import ErrorModal from '@/components/ui/ErrorModal.vue'
import SuccessModal from '@/components/modals/SuccessModal.vue'
import ErrorModal from '@/components/modals/ErrorModal.vue'
import FullPageLoadingComponent from '@/components/ui/FullPageLoadingComponent.vue'
import axios from '@/axios'
import { Log } from '@/modules/log'

View File

@ -1,30 +0,0 @@
<!--
CONFIRM MODAL
DESCRIPTION:
A MODAL THAT ALLOWS THE USER TO CONFIRM OR CANCEL AN OPERATION.
HOW TO USE:
First, import the component, put it in the template section, and then use
VBModal bootstrap-vue component and pass it modal-error argument to open the
modal.
EXAMPLE:
<b-button v-b-modal:modal-error>Add New User</b-button>
<ConfirmModal />
-->
<template>
<div>
</div>
</template>
<script>
export default {
name: 'ConfirmModalComponent'
}
</script>
<style scoped lang="scss">
</style>

View File

@ -33,6 +33,7 @@ EVENTS:
v-bind:label-for="name"
v-bind:label-class="labelClass"
v-bind:description="description"
v-bind:class="{ 'mb-0': description.trim() !== '' }"
>
<b-form-datepicker
v-bind:class="{ 'is-invalid': errors.length > 0 }"
@ -41,8 +42,12 @@ EVENTS:
v-on:input="$emit('input', MutableValue_D)"
v-bind:readonly="readonly"
/>
<small class="text-danger" style="white-space: pre;">&nbsp;{{ errors[0] }}</small>
<small v-if="description.trim() === ''" class="text-danger" style="white-space: pre;">&nbsp;{{ errors[0] }}</small>
</b-form-group>
<small v-if="description.trim() !== ''" class="text-danger d-block mb-1">
<span v-if="errors.length === 0">&nbsp;</span>
{{ errors[0] }}
</small>
</ValidationProvider>
</template>

View File

@ -0,0 +1,121 @@
<!--
DROPDOWN
DESCRIPTION:
ALLOWS SELECTING ONE OF THE PROVIDED OPTIONS, WITH ATTACHED VALIDATION.
PROPS:
-------
| Property Name | Type | Default Value | Description |
|-----------------|---------|---------------|--------------------------------------------------------------------------|
EVENTS:
--------
| Event Name | Description |
|------------|--------------------------------|
| input | invoked when the input changes |
-->
<template>
<ValidationProvider v-bind:name="name" v-bind:rules="Rules" v-slot="{ errors }">
<b-form-group
v-bind:label="label"
v-bind:label-for="name"
v-bind:label-class="labelClass"
v-bind:description="description"
v-bind:class="{ 'mb-0': description.trim() !== '' }"
>
<v-select
v-model="MutableValue_D"
v-on:input="$emit('input', MutableValue_D)"
v-bind:class="{ 'is-invalid': errors.length > 0 }"
v-bind:options="options"
v-bind:placeholder="placeholder"
label="title"
v-bind:clearable="false"
/>
<small v-if="description.trim() === ''" class="text-danger" style="white-space: pre;">&nbsp;{{ errors[0] }}</small>
</b-form-group>
<small v-if="description.trim() !== ''" class="text-danger d-block mb-1">
<span v-if="errors.length === 0">&nbsp;</span>
{{ errors[0] }}
</small>
</ValidationProvider>
</template>
<script>
import { BFormGroup } from 'bootstrap-vue'
import vSelect from 'vue-select'
import { required, email, numeric } from '@validations'
export default {
name: 'DropdownComponent',
props: {
// NOTE(pooya): `name` is checked by vee-validate so make sure it's
// provided inside @/libs/i18n/vee-validate-locales in the fields
// property.
value: { type: String , required: false , default: '' },
rules: { type: String , required: false , default: '' },
notRequired: { type: Boolean , required: false , default: false },
noValidation: { type: Boolean , required: false , default: false },
label: { type: String , required: false , default: '' },
labelClass: { type: String , required: false , default: '' },
description: { type: String , required: false , default: '' },
placeholder: { type: String , required: false , default: '' },
name: { type: String , required: false , default: '' },
// Must be an array value-title objects. For example:
// [ {value:0, title: 'Diomand'}, {value:1, title: 'Bronze'} ]
options: { type: Array , required: false , default: () => [] },
},
components: {
BFormGroup,
// Vue Select documentation:
// https://vue-select.org/api/props.html#appendtobody
vSelect,
},
data()
{
return {
MutableValue_D: this.value,
// validations rules
required,
email,
numeric,
}
},
watch: {
value(NewValue, OldValue)
{
this.MutableValue_D = NewValue
// (pooya): Should I emit an input event when the v-model
// is changed from the parent component or not?
// It logically doesn't make sense but will it lead to bugs?
//
// this.$emit('input', NewValue)
},
},
computed: {
Rules()
{
if( this.noValidation )
return ''
if( this.notRequired )
return (this.rules.trim()) ? `${this.rules}` : ''
else
return (this.rules.trim()) ? `required|${this.rules}` : 'required'
},
},
}
</script>

View File

@ -36,6 +36,7 @@ EVENTS:
v-bind:label-for="name"
v-bind:label-class="labelClass"
v-bind:description="description"
v-bind:class="{ 'mb-0': description.trim() !== '' }"
>
<b-form-textarea
v-if="multiline"
@ -59,8 +60,12 @@ EVENTS:
v-on:input="$emit('input', MutableValue_D)"
v-bind:readonly="readonly"
/>
<small class="text-danger" style="white-space: pre;">&nbsp;{{ errors[0] }}</small>
<small v-if="description.trim() === ''" class="text-danger" style="white-space: pre;">&nbsp;{{ errors[0] }}</small>
</b-form-group>
<small v-if="description.trim() !== ''" class="text-danger d-block mb-1">
<span v-if="errors.length === 0">&nbsp;</span>
{{ errors[0] }}
</small>
</ValidationProvider>
</template>

View File

@ -72,85 +72,79 @@
<a-table
@change="change"
:indentSize="5"
:pagination="false"
ref="tbl"
:locale="locale"
:columns="visibleCols"
:data-source="data"
:row-selection="options.is_row_selection ? { selectedRowKeys: selectedRowKeys, onChange: onChange } : null"
@change="change"
:indentSize="5"
:pagination="false"
ref="tbl"
:locale="locale"
:columns="visibleCols"
:data-source="data"
:row-selection="options.is_row_selection ? { selectedRowKeys: selectedRowKeys, onChange: onChange } : null"
>
<template :slot="item" slot-scope="name, record, index" v-for="(item,ind) in customRender">
<slot :name="item" :text="name" :record="record" :index="index"></slot>
</template>
<template :slot="item" slot-scope="name, record, index" v-for="(item,ind) in customRender">
<slot :name="item" :text="name" :record="record" :index="index"/>
</template>
<!-- <template slot="expandedRowRender" slot-scope="text, record, index">-->
<!-- <slot name="expandedRowRender" :text="text" :record="record" :index="index"/>-->
<!-- </template>-->
<!-- <template slot="expandedRowRender" slot-scope="text, record, index">-->
<!-- <slot name="expandedRowRender" :text="text" :record="record" :index="index"/>-->
<!-- </template>-->
<div slot="footer" slot-scope="{item}">
<div class="flex items-center">
<div class="flex-1">
<div v-if="query.total">
<span>{{query.total}}</span>
Record found.
</div>
</div>
<APagination
v-if="is_pagenation"
show-size-changer
:pageSizeOptions="['5','10','20']"
:page-size.sync="query.limit"
@change="changePage"
@showSizeChange="changePage"
:default-current="1" :total="query.total" v-model.sync="query.page">
<template slot="buildOptionText" slot-scope="props">
<div dir="rtl">
<span> {{ props.value }} </span>
<span>record</span>
</div>
</template>
</APagination>
</div>
</div>
<div v-for="item,index in getColsFilter" :slot="item.scopedSlots.filterDropdown"
slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }">
<div>
<TextXTableFilter v-if="item.filterType === 'default'"
:column="column"
@search="submitFilter"
/>
<SwitchXTableFilter v-if="item.filterType === 'switch'"
:column="column"
@search="submitFilter"
/>
<NumberXTableFilter v-if="item.filterType === 'number'"
:column="column"
@search="submitFilter"
/>
<SelectOptionXTableFilter v-if="item.filterType === 'select'" :column="item"
:set-selected-keys="setSelectedKeys" :selected-keys="selectedKeys"
:confirm="confirm" :clear-filters="clearFilters"/>
<DateXTableFilter v-if="item.filterType === 'date'"
:column="column"
@search="submitFilter"
/>
</div>
<a-icon :slot="item.scopedSlots.filterIcon" slot-scope="filtered" type="search"
:style="{ color: filtered ? '#108ee9' : undefined }"/>
</div>
<div slot="footer" slot-scope="{item}">
<div class="flex items-center">
<div class="flex-1">
<div v-if="query.total">
<span>{{query.total}}</span>
Record found.
</div>
</div>
<APagination
v-if="is_pagenation"
show-size-changer
:pageSizeOptions="['5','10','20']"
:page-size.sync="query.limit"
@change="changePage"
@showSizeChange="changePage"
:default-current="1"
:total="query.total"
v-model.sync="query.page"
>
<template slot="buildOptionText" slot-scope="props">
<div dir="rtl">
<span> {{ props.value }} </span>
<span>record</span>
</div>
</template>
</APagination>
</div>
</div>
<div v-for="item,index in getColsFilter" :slot="item.scopedSlots.filterDropdown"
slot-scope="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }">
<div>
<TextXTableFilter v-if="item.filterType === 'default'"
:column="column"
@search="submitFilter"
/>
<SwitchXTableFilter v-if="item.filterType === 'switch'"
:column="column"
@search="submitFilter"
/>
<NumberXTableFilter v-if="item.filterType === 'number'"
:column="column"
@search="submitFilter"
/>
<SelectOptionXTableFilter v-if="item.filterType === 'select'" :column="item"
:set-selected-keys="setSelectedKeys" :selected-keys="selectedKeys"
:confirm="confirm" :clear-filters="clearFilters"
/>
<DateXTableFilter v-if="item.filterType === 'date'"
:column="column"
@search="submitFilter"
/>
</div>
<a-icon :slot="item.scopedSlots.filterIcon" slot-scope="filtered" type="search"
:style="{ color: filtered ? '#108ee9' : undefined }"/>
</div>
</a-table>
</ASpin>
</ACard>
@ -169,7 +163,6 @@ import {openFullscreen, closeFullscreen} from "@/plugins/fullscreen";
import NumberXTableFilter from "./NumberXTableFilter";
import { BRow, BCol, BInputGroup, BInputGroupAppend, BFormInput, BFormCheckbox, BButton } from 'bootstrap-vue'
const list = [
{
title: 'emails',
@ -293,23 +286,30 @@ export default {
return this.query;
},
refresh($event){
refresh ($event)
{
this.$emit('refresh')
this.query.query = []
this.query.search = undefined
this.fetch()
},
async fetch() {
async fetch()
{
try {
this.loading = true;
if(this.method == "post"){
const {data: {rows, query}} = await axios.post(this.model.url, {...this.requestData()});
const {data: {rows, query}} = await axios.post(this.model.url, {
...this.requestData(),
te: {
p: { c: 1, s: 10 }
}
});
this.data = rows;
this.query.page = query.page
this.query.limit = query.limit;
this.query.total = query.total;
this.query.max_page = query.max_page;
// this.query.page = query.page
// this.query.limit = query.limit;
// this.query.total = query.total;
// this.query.max_page = query.max_page;
this.$emit('rows',rows)
}else{
const {data} = await axios.get(this.model.url);

View File

@ -1,19 +1,26 @@
import Vue from 'vue'
import FeatherIcon from '@core/components/feather-icon/FeatherIcon.vue'
import { ValidationObserver, ValidationProvider } from 'vee-validate'
import Textbox from '@/components/ui/Textbox.vue'
import Checkbox from '@/components/ui/Checkbox.vue'
import Datepicker from '@/components/ui/Datepicker.vue'
import ConfirmModal from '@/components/ui/ConfirmModal.vue'
import Dropdown from '@/components/ui/Dropdown.vue'
import ConfirmModal from '@/components/modals/ConfirmModal.vue'
import SuccessModal from '@/components/modals/SuccessModal.vue'
import ErrorModal from '@/components/modals/ErrorModal.vue'
import FullpageLoadingComponent from '@/components/ui/FullPageLoadingComponent.vue'
// Form Components
Vue.component(Textbox.name, Textbox)
Vue.component(Checkbox.name, Checkbox)
Vue.component(Datepicker.name, Datepicker)
Vue.component(Dropdown.name, Dropdown)
// Modals
// Modals/Dialogs
Vue.component(ConfirmModal.name, ConfirmModal)
Vue.component(SuccessModal.name, SuccessModal)
Vue.component(ErrorModal.name, ErrorModal)
// Validation
Vue.component('ValidationObserver', ValidationObserver)

View File

@ -2,10 +2,24 @@
"fields": {
"title": "Title",
"storage_limit": "Storage Limit",
"storage_usage": "Storage Usage",
"description": "Description",
"from_date": "Beginning Date",
"from_date": "Starting Date",
"to_date": "Ending Date",
"is_active": "Active"
"months": "Months",
"is_active": "Active",
"price": "Price",
"discounted_price": "Price",
"total_price": "Price",
"value": "Value",
"rule_type": "Rule Type",
"amount": "Amount",
"payment_dead_line": "Payment Deadline",
"allowed_delay_days": "Allowed Delay Days",
"installments_count": "Installments Count",
"payable_amount": "Payable Amount",
"payed_amount": "Payed Amount",
"user_limit": "User Limit"
},
"message": {
"title": "Card Title",
@ -31,7 +45,13 @@
"Add Law Firm": "Add Law Firm",
"Law Firm Reports": "Law Firm Reports",
"Subscriptions": "Subscriptions",
"Subscription Plans": "Subscription Plans",
"Subscription Plans": "Plans",
"Subscription Contracts": "Contracts",
"Subscription Rules": "Rules",
"Subscription Subscriptions": "Subscriptions",
"Subscription Transactions": "Transactions",
"Subscription Payments": "Payments",
"Subscription Contract Users": "Contract Users",
"Users": "Users",
"Users List": "Users List",

View File

@ -4,8 +4,12 @@
* *
* Table of Contents: *
* - Number Thousand Separator *
* - Format Bytes *
* - Logging *
*******************************************************************************/
* - Sleep *
* - View In Fullscreen *
* - Close Fullscreen *
******************************************************************************/
/*
|-----------------------------------------------------------------------
@ -59,7 +63,67 @@ export function FormatBytes(Bytes, Decimals = 2)
*/
export function Log(Message, Data = '')
{
console.log(`%c ${Message} `, 'background:black; color:#bada55; font-family:"PxPlus ToshibaSat 9x16"; font-size: 13px;')
console.log(`%c ${Message.toUpperCase()} `, 'background:black; color:#bada55; font-family:"PxPlus ToshibaSat 9x16"; font-size: 13px; border-radius: 4px; padding: 1px 3px;')
if( Data !== '' )
console.log(Data)
}
/*
|-----------------------------------------------------------------------
| Sleep
|-----------------------------------------------------------------------
|
| Suspends execution for an specified number of milliseconds.
|
| Usage Example:
|
| await Sleep(1000) // sleep for 1 second
|
|
| Reference:stackoverflow.com/questions/951021/what-is-the-javascript-version-of-sleep/39914235#39914235
|
*/
export function Sleep(Milliseconds)
{
return new Promise(Resolve => setTimeout(Resolve, Milliseconds))
}
/*
|-----------------------------------------------------------------------
| View In Fullscreen
|-----------------------------------------------------------------------
|
| Renders the specified HTML element in fullscreen.
|
*/
export function OpenFullscreen(Element)
{
if( Element.requestFullscreen )
Element.requestFullscreen()
else if( Element.mozRequestFullScreen ) /* Firefox */
Element.mozRequestFullScreen()
else if( Element.webkitRequestFullscreen ) /* Chrome, Safari and Opera */
Element.webkitRequestFullscreen()
else if( Element.msRequestFullscreen ) /* IE/Edge */
Element.msRequestFullscreen()
}
/*
|-----------------------------------------------------------------------
| Close Fullscreen
|-----------------------------------------------------------------------
|
| Closes fullscreen view if it's already open.
|
*/
export function closeFullscreen()
{
if( document.exitFullscreen )
document.exitFullscreen()
else if( document.mozCancelFullScreen ) /* Firefox */
document.mozCancelFullScreen()
else if( document.webkitExitFullscreen ) /* Chrome, Safari and Opera */
document.webkitExitFullscreen()
else if( document.msExitFullscreen ) /* IE/Edge */
document.msExitFullscreen()
}

View File

@ -138,6 +138,30 @@ export default [
title: 'Subscription Plans',
route: 'subscription-plans',
},
{
title: 'Subscription Contracts',
route: 'subscription-contracts',
},
{
title: 'Subscription Rules',
route: 'subscription-rules',
},
{
title: 'Subscription Subscriptions',
route: 'subscription-subscriptions',
},
{
title: 'Subscription Transactions',
route: 'subscription-transactions',
},
{
title: 'Subscription Payments',
route: 'subscription-payments',
},
// {
// title: 'Subscription Contract Users',
// route: 'subscription-contractusers',
// },
],
},
]

View File

@ -1,104 +0,0 @@
/*******************************************************************************
* Core *
* Contains various javascript utilities. *
* *
* Table of Contents: *
* - Number Thousand Separator *
* - Format Bytes *
* - Logging *
* - Is Number *
* - Sleep *
*******************************************************************************/
/*
|-----------------------------------------------------------------------
| Number Thousand Separator
|-----------------------------------------------------------------------
|
| A function which takes a number and a character and returns a string
| containing that number with the given character as thousands
| separators.
|
*/
export function SeparateNumberByThousands(Num, Separator = ',')
{
return Num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, Separator)
// P.S: Thanks Github Copilot for saving me numerous hours of stackoverflowing 🙂
}
/*
|-----------------------------------------------------------------------
| Format Bytes
|-----------------------------------------------------------------------
|
| Converts bytes to the equivalent kilobytes, megabytes, gigabytes, etc.
|
*/
export function FormatBytes(Bytes, Decimals = 2)
{
if( Bytes === 0 ) return '0 Bytes'
const K = 1024
const Dm = (Decimals < 0) ? 0 : Decimals
const Sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const I = Math.floor(Math.log(Bytes) / Math.log(K))
return parseFloat((Bytes / Math.pow(K, I)).toFixed(Dm)) + ' ' + Sizes[I]
}
/*
|-----------------------------------------------------------------------
| Logging
|-----------------------------------------------------------------------
|
| Logs a message to the browser console.
| This function is provided to abstract all your logging calls, so you
| can change the style of all of them at once. It is useful to make the
| logs of your application look different so you can find them easier
| when there are hundreds of logs in the console.
|
*/
export function Log(Message, Data = '')
{
console.log(
`%c ${Message.toUpperCase()} `,
'background:black; color:#bada55; font-family:"PxPlus ToshibaSat 9x16"; font-size: 13px; border-radius: 4px; padding: 1px 3px;')
if( Data !== '' )
console.log(Data)
}
/*
|-----------------------------------------------------------------------
| Is Number
|-----------------------------------------------------------------------
|
| Takes a string and determines whether it is a valid number or not.
| This function shields against common bugs like whitespace, implicit
| partial parsing, radix, coercion of arrays, etc.
|
| Reference: stackoverflow.com/questions/175739/how-can-i-check-if-a-string-is-a-valid-number
|
*/
export function IsNumber(TheString)
{
if( typeof str != "string" ) return false // we only process strings!
return !isNaN(str) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)...
!isNaN(parseFloat(str)) // ...and ensure strings of whitespace fail
}
/*
|-----------------------------------------------------------------------
| Sleep
|-----------------------------------------------------------------------
|
| Hangs execution for the specified milliseconds.
|
| Reference: stackoverflow.com/questions/951021/what-is-the-javascript-version-of-sleep/39914235#39914235
|
*/
export async function Sleep(Milliseconds)
{
await new Promise(r => setTimeout(r, Milliseconds))
}

View File

@ -138,6 +138,36 @@ export default [
{
path: '/subscriptions/plans',
name: 'subscription-plans',
component: () => import('@/views/Subscriptions/PlansList.vue'),
component: () => import('@/views/Subscriptions/Plans.vue'),
},
{
path: '/subscriptions/contracts',
name: 'subscription-contracts',
component: () => import('@/views/Subscriptions/Contracts.vue'),
},
{
path: '/subscriptions/rules',
name: 'subscription-rules',
component: () => import('@/views/Subscriptions/Rules.vue'),
},
{
path: '/subscriptions/subscriptions',
name: 'subscription-subscriptions',
component: () => import('@/views/Subscriptions/Subscriptions.vue'),
},
{
path: '/subscriptions/transactions',
name: 'subscription-transactions',
component: () => import('@/views/Subscriptions/Transactions.vue'),
},
{
path: '/subscriptions/payments',
name: 'subscription-payments',
component: () => import('@/views/Subscriptions/Payments.vue'),
},
{
path: '/subscriptions/contract-users',
name: 'subscription-contractusers',
component: () => import('@/views/Subscriptions/ContractUsers.vue'),
},
]

View File

@ -0,0 +1,237 @@
<template>
<!-- <div v-bind:class="{ 'page--main': Model_D === null }"> -->
<div class="page--main">
<FullpageLoadingComponent v-bind:show="Loading_D" />
<!------------------------------ Title ------------------------------>
<div class="d-flex justify-content-between">
<h3>Subscription Plans</h3>
<div v-if="Model_D !== null" class="sx2">
<!-- <b-button variant="danger" v-ripple.400="'rgba(255, 255, 255, 0.15)'">Remove</b-button> -->
<b-button
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OpenEditModal()"
>
Add
</b-button>
</div>
</div>
<!------------------------------- Body ------------------------------>
<b-card class="mt-1 position-relative" style="min-height: 95%;" body-class="p-0">
<!-- No Plans Exist -->
<div id="no-plans--container" v-if="Model_D === null" class="d-flex flex-column justify-content-center align-items-center">
<img src="@/assets/svg/hero.svg" alt="no-users-found">
<h4 class="mt-2" style="text-align: center;">You haven't created any subscription plans.</h4>
<!-- <p style="text-align: center;">Click the following button to create a new plan.</p> -->
<b-button
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OpenEditModal()"
>
Add Subscription Plan
</b-button>
</div>
<!-- List of Users -->
<div v-else>
<XTable ref="Table" v-bind:model="Model_D">
<template v-slot:storage_limit="{ record }">
{{ record.storage_limit }} KB
</template>
<template v-slot:from_date="{ record }">
{{ (new Date(record.from_date)).toLocaleDateString('en-US') }}
</template>
<template v-slot:to_date="{ record }">
{{ (new Date(record.to_date)).toLocaleDateString('en-US') }}
</template>
<template v-slot:is_active="{ record }">
<feather-icon v-if="record.is_active" icon="CheckIcon" size="22" />
<feather-icon v-else icon="XIcon" size="22" />
</template>
<template v-slot:actions="{ record }">
<span class="d-inline-flex" style="column-gap: 1rem;">
<feather-icon
icon="EditIcon"
size="16"
color="blue"
class="cursor-pointer"
v-on:click="OpenEditModal(record)"
/>
<feather-icon
icon="Trash2Icon"
size="16"
color="crimson"
class="cursor-pointer"
v-b-modal.confirm-modal
v-on:click="RecordId_D = record.id"
/>
</span>
</template>
</XTable>
</div>
</b-card>
<EditPlanModal
ref="EditModal"
v-bind:initData="EditModalInitialData_D"
v-bind:editing="IsEditing_D"
v-on:submit="OnModalSubmitted"
/>
<SuccessModal v-bind:message="SuccessMessage_D" />
<ErrorModal message="Operation failed." />
<ConfirmModal
title="Confirm Deletion"
message="Are you sure you want to delete this plan?"
v-on:confirm="OnDeleteConfirmed"
/>
</div>
</template>
<script>
import { BCard, BButton, VBModal } from 'bootstrap-vue'
import Ripple from 'vue-ripple-directive'
import EditPlanModal from './components/EditPlanModal'
import * as TableCol from "./tables/contractUsersTbl"
import XTable from "@/components/x-table/XTable"
export default {
name: 'SubscriptionContractUsersView',
components: {
BCard,
BButton,
EditPlanModal,
XTable,
},
directives: {
'b-modal': VBModal,
Ripple,
},
data() {
return {
Model_D: null,
Loading_D: false,
SuccessMessage_D: '',
RecordId_D: null, // The id for update or delete APIs
// The data to initialize the edit modal with.
EditModalInitialData_D: undefined,
// Whether we're editing or creating a new plan
IsEditing_D: null,
}
},
methods: {
OpenEditModal(Record)
{
if( Boolean(Record) )
{
this.IsEditing_D = true
this.RecordId_D = Record.id
}
else this.IsEditing_D = false
this.EditModalInitialData_D = Record
this.$bvModal.show('modal-edit-plan')
},
async OnModalSubmitted(Event)
{
// TODO(pooya): Remove this check when flow is checked.
if( this.IsEditing_D === null )
{
console.error('INTERNAL ERROR: IsEditing is null')
this.RecordId_D = null
return
}
try
{
this.Loading_D = true
if( this.IsEditing_D )
{
await this.$http.put(`admin/subscription-plan/${this.RecordId_D}`, Event)
this.SuccessMessage_D = "Subscription plan was updated successfully."
}
else // creating a new plan
{
await this.$http.post('admin/create/subscription-plan', Event)
this.SuccessMessage_D = "Subscription plan was added successfully."
}
this.$refs.Table.refresh()
this.$refs.EditModal.Clear()
this.$bvModal.show('success-modal')
}
catch( Err )
{
console.log('Error', Err)
this.$bvModal.show('error-modal')
}
finally
{
this.Loading_D = false
this.IsEditing_D = null
this.RecordId_D = null
}
},
async OnDeleteConfirmed(Event)
{
if( Event === 'ok' )
try
{
this.Loading_D = true
await this.$http.delete(`admin/subscription-plan/${this.RecordId_D}`)
this.$refs.Table.refresh()
this.SuccessMessage_D = "Subscription plan deleted successfully."
this.$bvModal.show('success-modal')
}
catch( Err )
{
this.$bvModal.show('error-modal')
}
finally
{
this.Loading_D = false
this.RecordId_D = null
}
else if( Event === 'cancel' )
this.RecordId_D = null
},
},
created()
{
this.Model_D = TableCol.default
},
}
</script>
<style scoped lang="scss">
#no-plans--container {
position: absolute;
bottom: -2.5rem;
top: 0;
left: 0;
right: 0;
gap: 0.25rem;
padding: 2rem;
img {
width: 280px; // 100%
user-select: none;
}
@media (min-width: 768px) {
width: auto;
}
}
</style>

View File

@ -0,0 +1,233 @@
<template>
<!-- <div v-bind:class="{ 'page--main': Model_D === null }"> -->
<div class="page--main">
<FullpageLoadingComponent v-bind:show="Loading_D" />
<!------------------------------ Title ------------------------------>
<div class="d-flex justify-content-between">
<h3>Subscription Contracts</h3>
<div v-if="Model_D !== null" class="sx2">
<!-- <b-button variant="danger" v-ripple.400="'rgba(255, 255, 255, 0.15)'">Remove</b-button> -->
<!-- <b-button
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OpenEditModal()"
>
Add
</b-button> -->
</div>
</div>
<!------------------------------- Body ------------------------------>
<b-card class="mt-1 position-relative" style="min-height: 95%;" body-class="p-0">
<!-- No Plans Exist -->
<div id="no-contracts--container" v-if="Model_D === null" class="d-flex flex-column justify-content-center align-items-center">
<img src="@/assets/svg/hero.svg" alt="no-users-found">
<h4 class="mt-2" style="text-align: center;">You haven't created any subscription contracts.</h4>
<!-- <p style="text-align: center;">Click the following button to create a new law firm.</p> -->
<b-button
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OpenEditModal()"
>
Add Subscription Contract
</b-button>
</div>
<!-- List of Users -->
<div v-else>
<XTable ref="Table" v-bind:model="Model_D">
<template v-slot:storage_limit="{ record }">
{{ record.storage_limit }} KB
</template>
<template v-slot:from_date="{ record }">
{{ (new Date(record.from_date)).toLocaleDateString('en-US') }}
</template>
<template v-slot:to_date="{ record }">
{{ (new Date(record.to_date)).toLocaleDateString('en-US') }}
</template>
<template v-slot:actions="{ record }">
<span class="d-inline-flex" style="column-gap: 1rem;">
<feather-icon
icon="EditIcon"
size="16"
color="blue"
class="cursor-pointer"
v-on:click="OpenEditModal(record)"
/>
<feather-icon
icon="Trash2Icon"
size="16"
color="crimson"
class="cursor-pointer"
v-b-modal.confirm-modal
v-on:click="RecordId_D = record.id"
/>
</span>
</template>
</XTable>
</div>
</b-card>
<EditContractModal
ref="EditModal"
v-bind:initData="EditModalInitialData_D"
v-bind:editing="IsEditing_D"
v-on:submit="OnModalSubmitted"
/>
<SuccessModal v-bind:message="SuccessMessage_D" />
<ErrorModal message="Operation failed." />
<ConfirmModal
title="Confirm Deletion"
message="Are you sure you want to delete this contract?"
v-on:confirm="OnDeleteConfirmed"
/>
</div>
</template>
<script>
import { BCard, BButton, VBModal } from 'bootstrap-vue'
import Ripple from 'vue-ripple-directive'
import EditContractModal from './components/EditContractModal.vue'
import * as TableCol from "./tables/contractsTbl"
import XTable from "@/components/x-table/XTable"
export default {
name: 'UsersListView',
components: {
BCard,
BButton,
EditContractModal,
XTable,
},
directives: {
'b-modal': VBModal,
Ripple,
},
data() {
return {
Model_D: null,
Loading_D: false,
SuccessMessage_D: '',
RecordId_D: null, // The id for update or delete APIs
// The data to initialize the edit modal with.
EditModalInitialData_D: undefined,
// Whether we're editing or creating a new plan
IsEditing_D: null,
}
},
methods: {
OpenEditModal(Record)
{
if( Boolean(Record) )
{
this.IsEditing_D = true
this.RecordId_D = Record.id
}
else this.IsEditing_D = false
this.EditModalInitialData_D = Record
this.$bvModal.show('modal-edit-plan')
},
async OnModalSubmitted(Event)
{
// TODO(pooya): Remove this check when flow is checked.
if( this.IsEditing_D === null )
{
console.error('INTERNAL ERROR: IsEditing is null')
this.RecordId_D = null
return
}
try
{
this.Loading_D = true
if( this.IsEditing_D )
{
await this.$http.put(`admin/subscription-contract/${this.RecordId_D}`, Event)
this.SuccessMessage_D = "Subscription contract was updated successfully."
}
else // creating a new contract
{
await this.$http.post('admin/create/subscription-contract', Event)
this.SuccessMessage_D = "Subscription contract was added successfully."
}
this.$refs.Table.refresh()
this.$refs.EditModal.Clear()
this.$bvModal.show('success-modal')
}
catch( Err )
{
console.log('Error', Err)
this.$bvModal.show('error-modal')
}
finally
{
this.Loading_D = false
this.IsEditing_D = null
this.RecordId_D = null
}
},
async OnDeleteConfirmed(Event)
{
if( Event === 'ok' )
try
{
this.Loading_D = true
await this.$http.delete(`admin/subscription-contract/${this.RecordId_D}`)
this.$refs.Table.refresh()
this.SuccessMessage_D = "Subscription contract deleted successfully."
this.$bvModal.show('success-modal')
}
catch( Err )
{
this.$bvModal.show('error-modal')
}
finally
{
this.Loading_D = false
this.RecordId_D = null
}
else if( Event === 'cancel' )
this.RecordId_D = null
},
},
created()
{
this.Model_D = TableCol.default
},
}
</script>
<style scoped lang="scss">
#no-contracts--container {
position: absolute;
bottom: -2.5rem;
top: 0;
left: 0;
right: 0;
gap: 0.25rem;
padding: 2rem;
img {
width: 280px; // 100%
user-select: none;
}
@media (min-width: 768px) {
width: auto;
}
}
</style>

View File

@ -0,0 +1,230 @@
<template>
<!-- <div v-bind:class="{ 'page--main': Model_D === null }"> -->
<div class="page--main">
<FullpageLoadingComponent v-bind:show="Loading_D" />
<!------------------------------ Title ------------------------------>
<div class="d-flex justify-content-between">
<h3>Subscription Payments</h3>
<div v-if="Model_D !== null" class="sx2">
<!-- <b-button variant="danger" v-ripple.400="'rgba(255, 255, 255, 0.15)'">Remove</b-button> -->
<!-- <b-button
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OpenEditModal()"
>
Add
</b-button> -->
</div>
</div>
<!------------------------------- Body ------------------------------>
<b-card class="mt-1 position-relative" style="min-height: 95%;" body-class="p-0">
<!-- No Plans Exist -->
<div id="no-payments--container" v-if="Model_D === null" class="d-flex flex-column justify-content-center align-items-center">
<img src="@/assets/svg/hero.svg" alt="no-payments-found">
<h4 class="mt-2" style="text-align: center;">You haven't created any subscription payments.</h4>
<!-- <p style="text-align: center;">Click the following button to create a new plan.</p> -->
<b-button
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OpenEditModal()"
>
Add Subscription Payment
</b-button>
</div>
<!-- List of Users -->
<div v-else>
<XTable ref="Table" v-bind:model="Model_D">
<template v-slot:payed_at="{ record }">
{{ (new Date(record.payed_at)).toLocaleString('en-US') }}
</template>
<template v-slot:payment_dead_line="{ record }">
{{ (new Date(record.payment_dead_line)).toLocaleString('en-US') }}
</template>
<template v-slot:actions="{ record }">
<span class="d-inline-flex" style="column-gap: 1rem;">
<feather-icon
icon="EditIcon"
size="16"
color="blue"
class="cursor-pointer"
v-on:click="OpenEditModal(record)"
/>
<feather-icon
icon="Trash2Icon"
size="16"
color="crimson"
class="cursor-pointer"
v-b-modal.confirm-modal
v-on:click="RecordId_D = record.id"
/>
</span>
</template>
</XTable>
</div>
</b-card>
<EditPaymentModal
ref="EditModal"
v-bind:initData="EditModalInitialData_D"
v-bind:editing="IsEditing_D"
v-on:submit="OnModalSubmitted"
/>
<SuccessModal v-bind:message="SuccessMessage_D" />
<ErrorModal message="Operation failed." />
<ConfirmModal
title="Confirm Deletion"
message="Are you sure you want to delete this plan?"
v-on:confirm="OnDeleteConfirmed"
/>
</div>
</template>
<script>
import { BCard, BButton, VBModal } from 'bootstrap-vue'
import Ripple from 'vue-ripple-directive'
import EditPaymentModal from './components/EditPaymentModal'
import * as TableCol from "./tables/paymentsTbl"
import XTable from "@/components/x-table/XTable"
export default {
name: 'UsersListView',
components: {
BCard,
BButton,
EditPaymentModal,
XTable,
},
directives: {
'b-modal': VBModal,
Ripple,
},
data() {
return {
Model_D: null,
Loading_D: false,
SuccessMessage_D: '',
RecordId_D: null, // The id for update or delete APIs
// The data to initialize the edit modal with.
EditModalInitialData_D: undefined,
// Whether we're editing or creating a new plan
IsEditing_D: null,
}
},
methods: {
OpenEditModal(Record)
{
if( Boolean(Record) )
{
this.IsEditing_D = true
this.RecordId_D = Record.id
}
else this.IsEditing_D = false
this.EditModalInitialData_D = Record
this.$bvModal.show('modal-edit-payment')
},
async OnModalSubmitted(Event)
{
// TODO(pooya): Remove this check when flow is checked.
if( this.IsEditing_D === null )
{
console.error('INTERNAL ERROR: IsEditing is null')
this.RecordId_D = null
return
}
try
{
this.Loading_D = true
if( this.IsEditing_D )
{
await this.$http.put(`admin/subscription-payment/${this.RecordId_D}`, Event)
this.SuccessMessage_D = "Subscription plan was updated successfully."
}
else // creating a new plan
{
await this.$http.post('admin/create/subscription-plan', Event)
this.SuccessMessage_D = "Subscription plan was added successfully."
}
this.$refs.Table.refresh()
this.$refs.EditModal.Clear()
this.$bvModal.show('success-modal')
}
catch( Err )
{
console.log('Error', Err)
this.$bvModal.show('error-modal')
}
finally
{
this.Loading_D = false
this.IsEditing_D = null
this.RecordId_D = null
}
},
async OnDeleteConfirmed(Event)
{
if( Event === 'ok' )
try
{
this.Loading_D = true
await this.$http.delete(`admin/subscription-payment/${this.RecordId_D}`)
this.$refs.Table.refresh()
this.SuccessMessage_D = "Subscription payment deleted successfully."
this.$bvModal.show('success-modal')
}
catch( Err )
{
this.$bvModal.show('error-modal')
}
finally
{
this.Loading_D = false
this.RecordId_D = null
}
else if( Event === 'cancel' )
this.RecordId_D = null
},
},
created()
{
this.Model_D = TableCol.default
},
}
</script>
<style scoped lang="scss">
#no-payments--container {
position: absolute;
bottom: -2.5rem;
top: 0;
left: 0;
right: 0;
gap: 0.25rem;
padding: 2rem;
img {
width: 280px; // 100%
user-select: none;
}
@media (min-width: 768px) {
width: auto;
}
}
</style>

View File

@ -0,0 +1,237 @@
<template>
<!-- <div v-bind:class="{ 'page--main': Model_D === null }"> -->
<div class="page--main">
<FullpageLoadingComponent v-bind:show="Loading_D" />
<!------------------------------ Title ------------------------------>
<div class="d-flex justify-content-between">
<h3>Subscription Plans</h3>
<div v-if="Model_D !== null" class="sx2">
<!-- <b-button variant="danger" v-ripple.400="'rgba(255, 255, 255, 0.15)'">Remove</b-button> -->
<b-button
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OpenEditModal()"
>
Add
</b-button>
</div>
</div>
<!------------------------------- Body ------------------------------>
<b-card class="mt-1 position-relative" style="min-height: 95%;" body-class="p-0">
<!-- No Plans Exist -->
<div id="no-plans--container" v-if="Model_D === null" class="d-flex flex-column justify-content-center align-items-center">
<img src="@/assets/svg/hero.svg" alt="no-users-found">
<h4 class="mt-2" style="text-align: center;">You haven't created any subscription plans.</h4>
<!-- <p style="text-align: center;">Click the following button to create a new plan.</p> -->
<b-button
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OpenEditModal()"
>
Add Subscription Plan
</b-button>
</div>
<!-- List of Users -->
<div v-else>
<XTable ref="Table" v-bind:model="Model_D">
<template v-slot:storage_limit="{ record }">
{{ record.storage_limit }} KB
</template>
<template v-slot:from_date="{ record }">
{{ (new Date(record.from_date)).toLocaleDateString('en-US') }}
</template>
<template v-slot:to_date="{ record }">
{{ (new Date(record.to_date)).toLocaleDateString('en-US') }}
</template>
<template v-slot:is_active="{ record }">
<feather-icon v-if="record.is_active" icon="CheckIcon" size="22" />
<feather-icon v-else icon="XIcon" size="22" />
</template>
<template v-slot:actions="{ record }">
<span class="d-inline-flex" style="column-gap: 1rem;">
<feather-icon
icon="EditIcon"
size="16"
color="blue"
class="cursor-pointer"
v-on:click="OpenEditModal(record)"
/>
<feather-icon
icon="Trash2Icon"
size="16"
color="crimson"
class="cursor-pointer"
v-b-modal.confirm-modal
v-on:click="RecordId_D = record.id"
/>
</span>
</template>
</XTable>
</div>
</b-card>
<EditPlanModal
ref="EditModal"
v-bind:initData="EditModalInitialData_D"
v-bind:editing="IsEditing_D"
v-on:submit="OnModalSubmitted"
/>
<SuccessModal v-bind:message="SuccessMessage_D" />
<ErrorModal message="Operation failed." />
<ConfirmModal
title="Confirm Deletion"
message="Are you sure you want to delete this plan?"
v-on:confirm="OnDeleteConfirmed"
/>
</div>
</template>
<script>
import { BCard, BButton, VBModal } from 'bootstrap-vue'
import Ripple from 'vue-ripple-directive'
import EditPlanModal from './components/EditPlanModal'
import * as TableCol from "./tables/plansTbl"
import XTable from "@/components/x-table/XTable"
export default {
name: 'UsersListView',
components: {
BCard,
BButton,
EditPlanModal,
XTable,
},
directives: {
'b-modal': VBModal,
Ripple,
},
data() {
return {
Model_D: null,
Loading_D: false,
SuccessMessage_D: '',
RecordId_D: null, // The id for update or delete APIs
// The data to initialize the edit modal with.
EditModalInitialData_D: undefined,
// Whether we're editing or creating a new plan
IsEditing_D: null,
}
},
methods: {
OpenEditModal(Record)
{
if( Boolean(Record) )
{
this.IsEditing_D = true
this.RecordId_D = Record.id
}
else this.IsEditing_D = false
this.EditModalInitialData_D = Record
this.$bvModal.show('modal-edit-plan')
},
async OnModalSubmitted(Event)
{
// TODO(pooya): Remove this check when flow is checked.
if( this.IsEditing_D === null )
{
console.error('INTERNAL ERROR: IsEditing is null')
this.RecordId_D = null
return
}
try
{
this.Loading_D = true
if( this.IsEditing_D )
{
await this.$http.put(`admin/subscription-plan/${this.RecordId_D}`, Event)
this.SuccessMessage_D = "Subscription plan was updated successfully."
}
else // creating a new plan
{
await this.$http.post('admin/create/subscription-plan', Event)
this.SuccessMessage_D = "Subscription plan was added successfully."
}
this.$refs.Table.refresh()
this.$refs.EditModal.Clear()
this.$bvModal.show('success-modal')
}
catch( Err )
{
console.log('Error', Err)
this.$bvModal.show('error-modal')
}
finally
{
this.Loading_D = false
this.IsEditing_D = null
this.RecordId_D = null
}
},
async OnDeleteConfirmed(Event)
{
if( Event === 'ok' )
try
{
this.Loading_D = true
await this.$http.delete(`admin/subscription-plan/${this.RecordId_D}`)
this.$refs.Table.refresh()
this.SuccessMessage_D = "Subscription plan deleted successfully."
this.$bvModal.show('success-modal')
}
catch( Err )
{
this.$bvModal.show('error-modal')
}
finally
{
this.Loading_D = false
this.RecordId_D = null
}
else if( Event === 'cancel' )
this.RecordId_D = null
},
},
created()
{
this.Model_D = TableCol.default
},
}
</script>
<style scoped lang="scss">
#no-plans--container {
position: absolute;
bottom: -2.5rem;
top: 0;
left: 0;
right: 0;
gap: 0.25rem;
padding: 2rem;
img {
width: 280px; // 100%
user-select: none;
}
@media (min-width: 768px) {
width: auto;
}
}
</style>

View File

@ -1,158 +0,0 @@
<template>
<div v-bind:class="{ 'page--main': Model_D === null }">
<FullpageLoadingComponent v-bind:show="Loading_D" />
<!----------------------------- Title ----------------------------->
<div class="d-flex justify-content-between">
<h3>Subscription Plans</h3>
<div v-if="Model_D !== null" class="sx2">
<b-button variant="danger" v-ripple.400="'rgba(255, 255, 255, 0.15)'">Remove</b-button>
<b-button
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-b-modal:modal-edit-plan
>
Add
</b-button>
</div>
</div>
<!------------------------------ Body ----------------------------->
<b-card class="mt-1 position-relative" style="min-height: 95%;" body-class="p-0">
<!-- No Plans Exist -->
<div id="no-plans--container" v-if="Model_D === null" class="d-flex flex-column justify-content-center align-items-center">
<img src="@/assets/svg/hero.svg" alt="no-users-found">
<h4 class="mt-2" style="text-align: center;">You haven't created any law firms.</h4>
<p style="text-align: center;">Click the following button to create a new law firm.</p>
<b-button
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-b-modal:modal-edit-plan
>
Add Subscription Plan
</b-button>
</div>
<!-- List of Users -->
<div v-else>
<XTable v-bind:model="Model_D">
<template v-slot:storage_limit="{ record }">
{{ record.storage_limit }} KB
</template>
<template v-slot:from_date="{ record }">
{{ (new Date(record.from_date)).toLocaleDateString('en-US') }}
</template>
<template v-slot:to_date="{ record }">
{{ (new Date(record.to_date)).toLocaleDateString('en-US') }}
</template>
<template v-slot:is_active="{ record }">
<feather-icon v-if="record.is_active" icon="CheckIcon" size="22" />
<feather-icon v-else icon="XIcon" size="22" />
</template>
<template v-slot:actions="{ record }">
<span class="d-inline-flex" style="column-gap: 1rem;">
<feather-icon
icon="EditIcon"
size="16"
color="blue"
class="cursor-pointer"
/>
<feather-icon
icon="Trash2Icon"
size="16"
color="crimson"
class="cursor-pointer"
v-on:click="onDeleteRecord(record.id)"
/>
</span>
</template>
</XTable>
</div>
</b-card>
<EditPlanModal v-on:submit="HandleNewPlanAdded" />
</div>
</template>
<script>
import { BCard, BButton, VBModal } from 'bootstrap-vue'
import Ripple from 'vue-ripple-directive'
import EditPlanModal from './components/EditPlanModal'
import * as TableCol from "./tables/plansTbl"
import XTable from "@/components/x-table/XTable"
export default {
name: 'UsersListView',
components: {
BCard,
BButton,
EditPlanModal,
XTable,
},
directives: {
'b-modal': VBModal,
Ripple,
},
data() {
return {
Model_D: null,
Loading_D: false,
}
},
methods: {
async HandleNewPlanAdded(event)
{
// if create
try
{
this.Loading_D = true
// const { data } = this.$http.post('', this.Payload_D)
}
catch
{}
finally
{
this.Loading_D = false
}
// if edit
console.log('event is', event)
},
async OnDeleteRecord(RecordId)
{
},
},
created()
{
this.Model_D = TableCol.default
},
}
</script>
<style scoped lang="scss">
#no-plans--container {
position: absolute;
bottom: -2.5rem;
top: 0;
left: 0;
right: 0;
gap: 0.25rem;
padding: 2rem;
img {
width: 100%;
user-select: none;
}
@media (min-width: 768px) {
width: auto;
}
}
</style>

View File

@ -0,0 +1,261 @@
<template>
<!-- <div v-bind:class="{ 'page--main': Model_D === null }"> -->
<div class="page--main">
<FullpageLoadingComponent v-bind:show="Loading_D" />
<!------------------------------ Title ------------------------------>
<div class="d-flex justify-content-between">
<h3>Subscription Rules</h3>
<div v-if="Model_D !== null" class="sx2">
<!-- <b-button variant="danger" v-ripple.400="'rgba(255, 255, 255, 0.15)'">Remove</b-button> -->
<!-- <b-button
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OpenEditModal()"
>
Add
</b-button> -->
</div>
</div>
<!------------------------------- Body ------------------------------>
<b-card class="mt-1 position-relative" style="min-height: 95%;" body-class="p-0">
<!-- No Plans Exist -->
<div id="no-rules--container" v-if="Model_D === null" class="d-flex flex-column justify-content-center align-items-center">
<img src="@/assets/svg/hero.svg" alt="no-users-found">
<h4 class="mt-2" style="text-align: center;">You haven't created any subscription rules.</h4>
<!-- <p style="text-align: center;">Click the following button to create a new plan.</p> -->
<b-button
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OpenEditModal()"
>
Add Subscription Rule
</b-button>
</div>
<!-- List of Users -->
<div v-else>
<XTable ref="Table" v-bind:model="Model_D">
<template v-slot:storage_limit="{ record }">
{{ record.storage_limit }} KB
</template>
<template v-slot:from_date="{ record }">
{{ (new Date(record.from_date)).toLocaleDateString('en-US') }}
</template>
<template v-slot:to_date="{ record }">
{{ (new Date(record.to_date)).toLocaleDateString('en-US') }}
</template>
<template v-slot:is_active="{ record }">
<feather-icon v-if="record.is_active" icon="CheckIcon" size="22" />
<feather-icon v-else icon="XIcon" size="22" />
</template>
<template v-slot:actions="{ record }">
<span class="d-inline-flex" style="column-gap: 1rem;">
<feather-icon
icon="EditIcon"
size="16"
color="blue"
class="cursor-pointer"
v-on:click="OpenEditModal(record)"
/>
<feather-icon
icon="Trash2Icon"
size="16"
color="crimson"
class="cursor-pointer"
v-b-modal.confirm-modal
v-on:click="RecordId_D = record.id"
/>
</span>
</template>
</XTable>
</div>
</b-card>
<EditRuleModal
ref="EditModal"
v-bind:initData="EditModalInitialData_D"
v-bind:plans="Plans_D"
v-bind:editing="IsEditing_D"
v-on:submit="OnModalSubmitted"
/>
<SuccessModal v-bind:message="SuccessMessage_D" />
<ErrorModal message="Operation failed." />
<ConfirmModal
title="Confirm Deletion"
message="Are you sure you want to delete this plan?"
v-on:confirm="OnDeleteConfirmed"
/>
</div>
</template>
<script>
import { BCard, BButton, VBModal } from 'bootstrap-vue'
import Ripple from 'vue-ripple-directive'
import EditRuleModal from './components/EditRuleModal'
import * as TableCol from "./tables/rulesTbl"
import XTable from "@/components/x-table/XTable"
export default {
name: 'UsersListView',
components: {
BCard,
BButton,
EditRuleModal,
XTable,
},
directives: {
'b-modal': VBModal,
Ripple,
},
data() {
return {
Model_D: null,
Loading_D: false,
SuccessMessage_D: '',
RecordId_D: null, // The id for update or delete APIs
Plans_D: [],
// The data to initialize the edit modal with.
EditModalInitialData_D: undefined,
// Whether we're editing or creating a new plan
IsEditing_D: null,
}
},
methods: {
OpenEditModal(Record)
{
if( Boolean(Record) )
{
this.IsEditing_D = true
this.RecordId_D = Record.id
}
else this.IsEditing_D = false
this.EditModalInitialData_D = Record
this.$bvModal.show('modal-edit-plan')
},
async OnModalSubmitted(Event)
{
// TODO(pooya): Remove this check when flow is checked.
if( this.IsEditing_D === null )
{
console.error('INTERNAL ERROR: IsEditing is null')
this.RecordId_D = null
return
}
try
{
this.Loading_D = true
if( this.IsEditing_D )
{
await this.$http.put(`admin/subscription-rule/${this.RecordId_D}`, Event)
this.SuccessMessage_D = "Subscription rule was updated successfully."
}
else // creating a new rule
{
await this.$http.post('admin/create/subscription-rule', Event)
this.SuccessMessage_D = "Subscription rule was added successfully."
}
this.$refs.Table.refresh()
this.$refs.EditModal.Clear()
this.$bvModal.show('success-modal')
}
catch( Err )
{
console.log('Error', Err)
this.$bvModal.show('error-modal')
}
finally
{
this.Loading_D = false
this.IsEditing_D = null
this.RecordId_D = null
}
},
async OnDeleteConfirmed(Event)
{
if( Event === 'ok' )
try
{
this.Loading_D = true
await this.$http.delete(`admin/subscription-rule/${this.RecordId_D}`)
this.$refs.Table.refresh()
this.SuccessMessage_D = "Subscription rule deleted successfully."
this.$bvModal.show('success-modal')
}
catch( Err )
{
this.$bvModal.show('error-modal')
}
finally
{
this.Loading_D = false
this.RecordId_D = null
}
else if( Event === 'cancel' )
this.RecordId_D = null
},
},
created()
{
this.Model_D = TableCol.default
},
async mounted()
{
try
{
this.Loading_D = true
const { data } = await this.$http.post('admin/list/subscription-plan', {
te: {
p: {c: 1, s: 100}
}
})
this.Plans_D = data.rows
console.log('plans', this.Plans_D)
}
catch( Err )
{
}
finally
{
this.Loading_D = false
}
},
}
</script>
<style scoped lang="scss">
#no-rules--container {
position: absolute;
bottom: -2.5rem;
top: 0;
left: 0;
right: 0;
gap: 0.25rem;
padding: 2rem;
img {
width: 280px; // 100%
user-select: none;
}
@media (min-width: 768px) {
width: auto;
}
}
</style>

View File

@ -0,0 +1,227 @@
<template>
<!-- <div v-bind:class="{ 'page--main': Model_D === null }"> -->
<div class="page--main">
<FullpageLoadingComponent v-bind:show="Loading_D" />
<!------------------------------ Title ------------------------------>
<div class="d-flex justify-content-between">
<h3>Subscriptions</h3>
<div v-if="Model_D !== null" class="sx2">
<!-- <b-button variant="danger" v-ripple.400="'rgba(255, 255, 255, 0.15)'">Remove</b-button> -->
<!-- <b-button
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OpenEditModal()"
>
Add
</b-button> -->
</div>
</div>
<!------------------------------- Body ------------------------------>
<b-card class="mt-1 position-relative" style="min-height: 95%;" body-class="p-0">
<!-- No Plans Exist -->
<div id="no-plans--container" v-if="Model_D === null" class="d-flex flex-column justify-content-center align-items-center">
<img src="@/assets/svg/hero.svg" alt="no-users-found">
<h4 class="mt-2" style="text-align: center;">You haven't created any subscription plans.</h4>
<!-- <p style="text-align: center;">Click the following button to create a new plan.</p> -->
<b-button
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OpenEditModal()"
>
Add Subscription Plan
</b-button>
</div>
<!-- List of Users -->
<div v-else>
<XTable ref="Table" v-bind:model="Model_D">
<template v-slot:last_deposit_date="{ record }">
{{ (new Date(record.last_deposit_date)).toLocaleString('en-US') }}
</template>
<template v-slot:actions="{ record }">
<span class="d-inline-flex" style="column-gap: 1rem;">
<feather-icon
icon="EditIcon"
size="16"
color="blue"
class="cursor-pointer"
v-on:click="OpenEditModal(record)"
/>
<feather-icon
icon="Trash2Icon"
size="16"
color="crimson"
class="cursor-pointer"
v-b-modal.confirm-modal
v-on:click="RecordId_D = record.id"
/>
</span>
</template>
</XTable>
</div>
</b-card>
<EditPlanModal
ref="EditModal"
v-bind:initData="EditModalInitialData_D"
v-bind:editing="IsEditing_D"
v-on:submit="OnModalSubmitted"
/>
<SuccessModal v-bind:message="SuccessMessage_D" />
<ErrorModal message="Operation failed." />
<ConfirmModal
title="Confirm Deletion"
message="Are you sure you want to delete this plan?"
v-on:confirm="OnDeleteConfirmed"
/>
</div>
</template>
<script>
import { BCard, BButton, VBModal } from 'bootstrap-vue'
import Ripple from 'vue-ripple-directive'
import EditPlanModal from './components/EditPlanModal'
import * as TableCol from "./tables/subscriptionsTbl"
import XTable from "@/components/x-table/XTable"
export default {
name: 'UsersListView',
components: {
BCard,
BButton,
EditPlanModal,
XTable,
},
directives: {
'b-modal': VBModal,
Ripple,
},
data() {
return {
Model_D: null,
Loading_D: false,
SuccessMessage_D: '',
RecordId_D: null, // The id for update or delete APIs
// The data to initialize the edit modal with.
EditModalInitialData_D: undefined,
// Whether we're editing or creating a new plan
IsEditing_D: null,
}
},
methods: {
OpenEditModal(Record)
{
if( Boolean(Record) )
{
this.IsEditing_D = true
this.RecordId_D = Record.id
}
else this.IsEditing_D = false
this.EditModalInitialData_D = Record
this.$bvModal.show('modal-edit-plan')
},
async OnModalSubmitted(Event)
{
// TODO(pooya): Remove this check when flow is checked.
if( this.IsEditing_D === null )
{
console.error('INTERNAL ERROR: IsEditing is null')
this.RecordId_D = null
return
}
try
{
this.Loading_D = true
if( this.IsEditing_D )
{
await this.$http.put(`admin/subscription-plan/${this.RecordId_D}`, Event)
this.SuccessMessage_D = "Subscription plan was updated successfully."
}
else // creating a new plan
{
await this.$http.post('admin/create/subscription-plan', Event)
this.SuccessMessage_D = "Subscription plan was added successfully."
}
this.$refs.Table.refresh()
this.$refs.EditModal.Clear()
this.$bvModal.show('success-modal')
}
catch( Err )
{
console.log('Error', Err)
this.$bvModal.show('error-modal')
}
finally
{
this.Loading_D = false
this.IsEditing_D = null
this.RecordId_D = null
}
},
async OnDeleteConfirmed(Event)
{
if( Event === 'ok' )
try
{
this.Loading_D = true
await this.$http.delete(`admin/subscription-plan/${this.RecordId_D}`)
this.$refs.Table.refresh()
this.SuccessMessage_D = "Subscription plan deleted successfully."
this.$bvModal.show('success-modal')
}
catch( Err )
{
this.$bvModal.show('error-modal')
}
finally
{
this.Loading_D = false
this.RecordId_D = null
}
else if( Event === 'cancel' )
this.RecordId_D = null
},
},
created()
{
this.Model_D = TableCol.default
},
}
</script>
<style scoped lang="scss">
#no-plans--container {
position: absolute;
bottom: -2.5rem;
top: 0;
left: 0;
right: 0;
gap: 0.25rem;
padding: 2rem;
img {
width: 280px; // 100%
user-select: none;
}
@media (min-width: 768px) {
width: auto;
}
}
</style>

View File

@ -0,0 +1,79 @@
<template>
<!-- <div v-bind:class="{ 'page--main': Model_D === null }"> -->
<div class="page--main">
<!------------------------------ Title ------------------------------>
<div class="d-flex justify-content-between">
<h3>Subscription Transactions</h3>
</div>
<!------------------------------- Body ------------------------------>
<b-card class="mt-1 position-relative" style="min-height: 95%;" body-class="p-0">
<!-- No Plans Exist -->
<div id="no-plans--container" v-if="Model_D === null" class="d-flex flex-column justify-content-center align-items-center">
<img src="@/assets/svg/hero.svg" alt="no-transactions-found">
<h4 class="mt-2" style="text-align: center;">There's no transaction yet.</h4>
</div>
<!-- List of Users -->
<div v-else>
<XTable ref="Table" v-bind:model="Model_D">
<template v-slot:created_at="{ record }">
{{ (new Date(record.created_at)).toLocaleString('en-US') }}
</template>
<template v-slot:is_active="{ record }">
<feather-icon v-if="record.is_active" icon="CheckIcon" size="22" />
<feather-icon v-else icon="XIcon" size="22" />
</template>
</XTable>
</div>
</b-card>
</div>
</template>
<script>
import { BCard } from 'bootstrap-vue'
import * as TableCol from "./tables/transactionsTbl"
import XTable from "@/components/x-table/XTable"
export default {
name: 'UsersListView',
components: {
BCard,
XTable,
},
data() {
return {
Model_D: null,
}
},
created()
{
this.Model_D = TableCol.default
},
}
</script>
<style scoped lang="scss">
#no-transactions--container {
position: absolute;
bottom: -2.5rem;
top: 0;
left: 0;
right: 0;
gap: 0.25rem;
padding: 2rem;
img {
width: 280px; // 100%
user-select: none;
}
@media (min-width: 768px) {
width: auto;
}
}
</style>

View File

@ -0,0 +1,223 @@
<template>
<b-modal id="modal-edit-plan" v-bind:centered="true" size="lg" body-class="pb-0">
<!-- Header -->
<template v-slot:modal-header="{ close }">
<h2 class="text-primary m-0">
<span v-if="editing">Edit Contract</span>
<span v-else>Add Contract</span>
</h2>
<img class="cursor-pointer" src="@/assets/svg/cross.svg" alt="close-icon" v-on:click="close()">
</template>
<!-- Body -->
<template v-slot:default>
<ValidationObserver ref="form" v-slot="{}">
<b-form class="d-flex flex-column" v-on:submit.prevent>
<b-row>
<b-col md="6">
<TextboxComponent
v-model="Payload_D.allowed_delay_days"
label="Allowed Delay Days"
name="allowed_delay_days"
placeholder="0"
rules="numeric"
/>
</b-col>
<b-col md="6">
<TextboxComponent
v-model="Payload_D.installments_count"
label="Installments Count"
name="installments_count"
placeholder="0"
rules="numeric"
/>
</b-col>
<b-col md="6">
<TextboxComponent
v-model="Payload_D.total_price"
label="Price"
name="total_price"
rules="numeric"
/>
</b-col>
<b-col md="6">
<TextboxComponent
v-model="Payload_D.discounted_price"
label="Discounted Price"
name="discounted_price"
rules="numeric"
/>
</b-col>
<b-col md="6">
<TextboxComponent
v-model="Payload_D.payable_amount"
label="Payable Amount"
name="payable_amount"
rules="numeric"
/>
</b-col>
<b-col md="6">
<TextboxComponent
v-model="Payload_D.payed_amount"
label="Payed Amount"
name="payed_amount"
rules="numeric"
/>
</b-col>
<b-col md="6">
<TextboxComponent
v-model="Payload_D.storage_limit"
label="Storage Limit (KB)"
name="storage_limit"
placeholder="10"
rules="numeric"
/>
</b-col>
<b-col md="6">
<TextboxComponent
v-model="Payload_D.user_limit"
label="User Limit"
name="user_limit"
placeholder="10"
rules="numeric"
/>
</b-col>
<b-col md="6">
<DatepickerComponent
v-model="Payload_D.from_date"
label="From"
name="from_date"
description="The contract is available to clients from this date"
/>
</b-col>
<b-col md="6">
<DatepickerComponent
v-model="Payload_D.to_date"
label="To"
name="to_date"
description="To this date"
/>
</b-col>
</b-row>
</b-form>
</ValidationObserver>
</template>
<!-- Footer -->
<template v-slot:modal-footer="{ hide }">
<b-button
class="mr-1"
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OnSubmit(hide)"
>
<span v-if="editing">Update Contract</span>
<span v-else>Save Contract</span>
</b-button>
or
<a class="text-primary" v-on:click="hide()">Cancel</a>
</template>
</b-modal>
</template>
<script>
import { BForm, BButton, BRow, BCol } from 'bootstrap-vue'
import Ripple from 'vue-ripple-directive'
export default {
name: 'EditPlanModalComponent',
components: {
BForm,
BButton,
BRow,
BCol,
},
directives: {
Ripple,
},
props: {
// The initial data to fill the inputs with.
initData: {
type: Object,
required: false,
default: () => ({
allowed_delay_days: '',
installments_count: '',
total_price: '',
discounted_price: '',
payable_amount: '',
payed_amount: '',
storage_limit: '',
// storage_usage: '',
from_date: '',
to_date: '',
user_limit: '',
})
},
// Whether we're in editing mode or create mode.
// It's type is Boolean | null.
editing: {
required: true,
},
},
data() {
return {
Payload_D: this.initData,
}
},
methods: {
OnSubmit(Hide)
{
this.$refs.form.validate().then(async success => {
if( success )
{
// NOTE(pooya): Interestingly, if you remove the
// following line, the payload will be passed as empty!
const tmp = Object.assign({}, this.Payload_D)
this.$emit('submit', tmp)
Hide()
}
})
},
Clear()
{
this.Payload_D.allowed_delay_days = ''
this.Payload_D.discounted_price = ''
this.Payload_D.total_price = ''
this.Payload_D.payable_amount = ''
this.Payload_D.payed_amount = ''
this.Payload_D.storage_limit = ''
this.Payload_D.from_date = ''
this.Payload_D.to_date = ''
this.Payload_D.user_limit = ''
},
},
watch: {
initData: function(NewValue) {
if( NewValue === undefined ) this.Clear()
else
{
this.Payload_D.title = NewValue.title
this.Payload_D.storage_limit = NewValue.storage_limit.toString()
this.Payload_D.description = NewValue.description
this.Payload_D.from_date = NewValue.from_date
this.Payload_D.to_date = NewValue.to_date
this.Payload_D.months = NewValue.months.toString()
this.Payload_D.is_active = NewValue.is_active
}
},
},
}
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,144 @@
<template>
<b-modal id="modal-edit-payment" v-bind:centered="true" size="lg" body-class="pb-0">
<!-- Header -->
<template v-slot:modal-header="{ close }">
<h2 class="text-primary m-0">
<span v-if="editing">Edit Plan</span>
<span v-else>Add Plan</span>
</h2>
<img class="cursor-pointer" src="@/assets/svg/cross.svg" alt="close-icon" v-on:click="close()">
</template>
<!-- Body -->
<template v-slot:default>
<ValidationObserver ref="form" v-slot="{}">
<b-form class="d-flex flex-column" v-on:submit.prevent>
<b-row>
<b-col md="6">
<TextboxComponent
v-model="Payload_D.amount"
label="Amount"
name="amount"
/>
</b-col>
<b-col md="6">
<DatepickerComponent
v-model="Payload_D.payment_dead_line"
label="Payment Deadline"
name="payment_dead_line"
/>
</b-col>
</b-row>
</b-form>
</ValidationObserver>
</template>
<!-- Footer -->
<template v-slot:modal-footer="{ hide }">
<b-button
class="mr-1"
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OnSubmit(hide)"
>
<span v-if="editing">Update Plan</span>
<span v-else>Save Plan</span>
</b-button>
or
<a class="text-primary" v-on:click="hide()">Cancel</a>
</template>
</b-modal>
</template>
<script>
import { BForm, BButton, BRow, BCol } from 'bootstrap-vue'
import Ripple from 'vue-ripple-directive'
export default {
name: 'EditPlanModalComponent',
components: {
BForm,
BButton,
BRow,
BCol,
},
directives: {
Ripple,
},
props: {
// The initial data to fill the inputs with.
initData: {
type: Object,
required: false,
default: () => ({
amount: '',
// payed_at: '',
payment_dead_line: '',
// tracking_code: '',
})
},
// Whether we're in editing mode or create mode.
// It's type is Boolean | null.
editing: {
required: true,
},
},
data() {
return {
Payload_D: this.initData,
}
},
methods: {
OnSubmit(Hide)
{
this.$refs.form.validate().then(async success => {
if( success )
{
// NOTE(pooya): Interestingly, if you remove the
// following line, the payload will be passed as empty!
const tmp = Object.assign({}, this.Payload_D)
this.$emit('submit', tmp)
Hide()
}
})
},
Clear()
{
this.Payload_D.title = ''
this.Payload_D.storage_limit = ''
this.Payload_D.description = ''
this.Payload_D.from_date = ''
this.Payload_D.to_date = ''
this.Payload_D.months = '1'
this.Payload_D.is_active = true
},
},
watch: {
initData: function(NewValue) {
if( NewValue === undefined ) this.Clear()
else
{
this.Payload_D.title = NewValue.title
this.Payload_D.storage_limit = NewValue.storage_limit.toString()
this.Payload_D.description = NewValue.description
this.Payload_D.from_date = NewValue.from_date
this.Payload_D.to_date = NewValue.to_date
this.Payload_D.months = NewValue.months.toString()
this.Payload_D.is_active = NewValue.is_active
}
},
},
}
</script>
<style scoped lang="scss">
</style>

View File

@ -1,29 +1,11 @@
<!--
Documentation:
Description:
A modal that prompts for a subscription plan.
How to use:
Import the component, put it in the template section, then use VBModal
bootstrap-vue component and pass it modal-edit-plan argument to open the modal.
Example:
<b-button v-b-modal:modal-edit-plan>Add New Plan</b-button>
<EditPlanModal v-on:submit="HandleNewPlanAdded" />
Events
Name Parameters Description
-------- ----------- ----------------------------------------------------------
submit payload called when the submit button (the button in the footer) is clicked
-->
<template>
<b-modal id="modal-edit-plan" v-bind:centered="true" size="lg" body-class="pb-0">
<!-- Header -->
<template v-slot:modal-header="{ close }">
<h2 class="text-primary m-0">Add Plan</h2>
<h2 class="text-primary m-0">
<span v-if="editing">Edit Plan</span>
<span v-else>Add Plan</span>
</h2>
<img class="cursor-pointer" src="@/assets/svg/cross.svg" alt="close-icon" v-on:click="close()">
</template>
@ -64,6 +46,7 @@ submit payload called when the submit button (the button in the footer
v-model="Payload_D.from_date"
label="From"
name="from_date"
description="The plan is available to clients from this date"
/>
</b-col>
<b-col md="6">
@ -71,9 +54,20 @@ submit payload called when the submit button (the button in the footer
v-model="Payload_D.to_date"
label="To"
name="to_date"
description="To this date"
/>
</b-col>
<b-col md="12">
<b-col md="6">
<TextboxComponent
v-model="Payload_D.months"
label="Months"
name="months"
placeholder="12"
description="How many months the plan will be active"
rules="numeric"
/>
</b-col>
<b-col md="6">
<CheckboxComponent
v-model="Payload_D.is_active"
label="Is Active"
@ -93,7 +87,8 @@ submit payload called when the submit button (the button in the footer
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OnSubmit(hide)"
>
Save Plan
<span v-if="editing">Update Plan</span>
<span v-else>Save Plan</span>
</b-button>
or
<a class="text-primary" v-on:click="hide()">Cancel</a>
@ -102,7 +97,7 @@ submit payload called when the submit button (the button in the footer
</template>
<script>
import { BForm, BButton, BRow, BCol, BFormGroup, BFormDatepicker } from 'bootstrap-vue'
import { BForm, BButton, BRow, BCol } from 'bootstrap-vue'
import Ripple from 'vue-ripple-directive'
export default {
@ -113,30 +108,38 @@ export default {
BButton,
BRow,
BCol,
BFormGroup,
BFormDatepicker,
},
directives: {
Ripple,
},
data() {
return {
Payload_D: {
props: {
// The initial data to fill the inputs with.
initData: {
type: Object,
required: false,
default: () => ({
title: '',
storage_limit: '',
description: '',
from_date: '',
to_date: '',
months: '1',
is_active: true,
},
PlanOptions_D: [
{ value: 0, title: 'Diamond' },
{ value: 1, title: 'Gold' },
{ value: 2, title: 'Silver' },
{ value: 3, title: 'Bronze' },
],
})
},
// Whether we're in editing mode or create mode.
// It's type is Boolean | null.
editing: {
required: true,
},
},
data() {
return {
Payload_D: this.initData,
}
},
@ -144,13 +147,44 @@ export default {
OnSubmit(Hide)
{
this.$refs.form.validate().then(async success => {
if (success)
if( success )
{
this.$emit('submit', this.Payload_D)
// NOTE(pooya): Interestingly, if you remove the
// following line, the payload will be passed as empty!
const tmp = Object.assign({}, this.Payload_D)
this.$emit('submit', tmp)
Hide()
}
})
},
Clear()
{
this.Payload_D.title = ''
this.Payload_D.storage_limit = ''
this.Payload_D.description = ''
this.Payload_D.from_date = ''
this.Payload_D.to_date = ''
this.Payload_D.months = '1'
this.Payload_D.is_active = true
},
},
watch: {
initData: function(NewValue) {
if( NewValue === undefined ) this.Clear()
else
{
this.Payload_D.title = NewValue.title
this.Payload_D.storage_limit = NewValue.storage_limit.toString()
this.Payload_D.description = NewValue.description
this.Payload_D.from_date = NewValue.from_date
this.Payload_D.to_date = NewValue.to_date
this.Payload_D.months = NewValue.months.toString()
this.Payload_D.is_active = NewValue.is_active
}
},
},
}
</script>

View File

@ -0,0 +1,170 @@
<template>
<b-modal id="modal-edit-plan" v-bind:centered="true" size="lg" body-class="pb-0">
<!-- Header -->
<template v-slot:modal-header="{ close }">
<h2 class="text-primary m-0">
<span v-if="editing">Edit Rules</span>
<span v-else>Add Rule</span>
</h2>
<img class="cursor-pointer" src="@/assets/svg/cross.svg" alt="close-icon" v-on:click="close()">
</template>
<!-- Body -->
<template v-slot:default>
<ValidationObserver ref="form" v-slot="{}">
<b-form class="d-flex flex-column" v-on:submit.prevent>
<b-row>
<b-col md="4">
<TextboxComponent
v-model="Payload_D.price"
label="Price"
name="price"
rules="numeric"
/>
</b-col>
<b-col md="4">
<TextboxComponent
v-model="Payload_D.discounted_price"
label="Discounted Price"
name="discounted_price"
rules="numeric"
/>
</b-col>
<b-col md="4">
<TextboxComponent
v-model="Payload_D.value"
label="Description"
name="description"
/>
</b-col>
<b-col md="4">
<DropdownComponent
v-model="Payload_D.rule_type"
label="Rule Type"
name="rule_type"
v-bind:options="RuleTypeOptions_D"
placeholder="Select a rule type"
/>
</b-col>
</b-row>
</b-form>
{{ Payload_D }}
</ValidationObserver>
</template>
<!-- Footer -->
<template v-slot:modal-footer="{ hide }">
<b-button
class="mr-1"
variant="primary"
v-ripple.400="'rgba(255, 255, 255, 0.15)'"
v-on:click="OnSubmit(hide)"
>
<span v-if="editing">Update Rule</span>
<span v-else>Save Rule</span>
</b-button>
or
<a class="text-primary" v-on:click="hide()">Cancel</a>
</template>
</b-modal>
</template>
<script>
import { BForm, BButton, BRow, BCol } from 'bootstrap-vue'
import Ripple from 'vue-ripple-directive'
export default {
name: 'EditPlanModalComponent',
components: {
BForm,
BButton,
BRow,
BCol,
},
directives: {
Ripple,
},
props: {
// The initial data to fill the inputs with.
initData: {
type: Object,
required: false,
default: () => ({
price: '',
discounted_price: '',
value: '',
rule_type: '',
plan_id: '1',
})
},
// Whether we're in editing mode or create mode.
// It's type is Boolean | null.
editing: {
required: true,
},
plans: {
type: Array,
required: false,
default: () => []
},
},
data() {
return {
Payload_D: this.initData,
RuleTypeOptions_D: [
{ value: 1, title: 'General' },
{ value: 2, title: 'Admin' },
{ value: 3, title: 'User' },
],
}
},
methods: {
OnSubmit(Hide)
{
this.$refs.form.validate().then(async success => {
if( success )
{
// NOTE(pooya): Interestingly, if you remove the
// following line, the payload will be passed as empty!
const tmp = Object.assign({}, this.Payload_D)
this.$emit('submit', tmp)
Hide()
}
})
},
Clear()
{
this.Payload_D.price = ''
this.Payload_D.discounted_price = ''
this.Payload_D.value = ''
this.Payload_D.rule_type = '1'
},
},
watch: {
initData: function(NewValue) {
if( NewValue === undefined ) this.Clear()
else
{
this.Payload_D.price = NewValue.price
this.Payload_D.discounted_price = NewValue.discounted_price
this.Payload_D.value = NewValue.value
this.Payload_D.rule_type = NewValue.rule_type
}
},
},
}
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,12 @@
import XTbl, { Xtc } from '@/components/x-table/index'
const tbl = new XTbl('admin/list/subscription-contract-user', 'Subscription Contract Users')
// tbl.add(new Xtc('title', 'Title'))
// tbl.add(new Xtc('description', 'Description'))
// tbl.add(new Xtc('storage_limit', 'Storage Limit').noSort().renderSlot('storage_limit'))
// tbl.add(new Xtc('from_date', 'From').noSort().renderSlot('from_date'))
// tbl.add(new Xtc('to_date', 'To').noSort().renderSlot('to_date'))
// tbl.add(new Xtc('months', 'Active Months'))
tbl.add(new Xtc('action', 'Actions').noSort().renderSlot('actions'))
export default tbl

View File

@ -0,0 +1,28 @@
import XTbl, { Xtc } from '@/components/x-table/index'
const tbl = new XTbl('admin/list/subscription-contract', 'Subscription Contracts')
tbl.add(new Xtc('allowed_delay_days', 'Allowed Delay Days'))
tbl.add(new Xtc('installments_count', 'Installments Count'))
tbl.add(new Xtc('total_price', 'Price'))
tbl.add(new Xtc('discounted_price', 'Discounted Price'))
tbl.add(new Xtc('payable_amount', 'Payable Amount'))
tbl.add(new Xtc('payed_amount', 'Payed Amount'))
tbl.add(new Xtc('storage_limit', 'Storage Limit').noSort().renderSlot('storage_limit'))
tbl.add(new Xtc('storage_usage', 'Storage Usage').noSort())
tbl.add(new Xtc('from_date', 'From').noSort().renderSlot('from_date'))
tbl.add(new Xtc('to_date', 'To').noSort().renderSlot('to_date'))
tbl.add(new Xtc('user_limit', 'User Limit'))
// tbl.add(new Xtc('action', 'Actions').noSort().renderSlot('actions'))
export default tbl
// What are the following?
// installments_count: 1
// status: 2
// type: 3
// user_usage: 1
// owner_id: 14
// plan_id: null

View File

@ -0,0 +1,10 @@
import XTbl, { Xtc } from '@/components/x-table/index'
const tbl = new XTbl('admin/list/subscription-payment', 'Subscription Payments')
tbl.add(new Xtc('amount', 'Amount'))
tbl.add(new Xtc('payed_at', 'Payed At').noSort().renderSlot('payed_at'))
tbl.add(new Xtc('payment_dead_line', 'Payment Deadline').noSort().renderSlot('payment_dead_line'))
tbl.add(new Xtc('tracking_code', 'Tracking_Code'))
// tbl.add(new Xtc('action', 'Actions').noSort().renderSlot('actions'))
export default tbl

View File

@ -1,11 +1,12 @@
import XTbl, { Xtc } from '@/components/x-table/index'
const tbl = new XTbl('admin/list/subscription-plan', 'Law Firms')
const tbl = new XTbl('admin/list/subscription-plan', 'Subscription Plans')
tbl.add(new Xtc('title', 'Title'))
tbl.add(new Xtc('description', 'Description'))
tbl.add(new Xtc('storage_limit', 'Storage Limit').noSort().renderSlot('storage_limit'))
tbl.add(new Xtc('from_date', 'From').noSort().renderSlot('from_date'))
tbl.add(new Xtc('to_date', 'To').noSort().renderSlot('to_date'))
tbl.add(new Xtc('months', 'Active Months'))
tbl.add(new Xtc('is_active', 'Is Active').noSort().renderSlot('is_active'))
tbl.add(new Xtc('action', 'Actions').noSort().renderSlot('actions'))

View File

@ -0,0 +1,21 @@
import XTbl, { Xtc } from '@/components/x-table/index'
const tbl = new XTbl('admin/list/subscription-rule', 'Subscription Plans')
tbl.add(new Xtc('price', 'Price'))
tbl.add(new Xtc('discounted_price', 'Discounted Price'))
tbl.add(new Xtc('value', 'Value'))
tbl.add(new Xtc('plan_id', 'Plan'))
tbl.add(new Xtc('priority', 'Priority'))
tbl.add(new Xtc('rule_type', 'Rule Type'))
// tbl.add(new Xtc('action', 'Actions').noSort().renderSlot('actions'))
export default tbl
// plan_id: 1
// priority: 1
// rule_type: 1

View File

@ -0,0 +1,11 @@
import XTbl, { Xtc } from '@/components/x-table/index'
const tbl = new XTbl('admin/list/subscription', 'Subscriptions')
tbl.add(new Xtc('charge', 'Charge'))
// tbl.add(new Xtc('storage_limit', 'Storage Limit').noSort().renderSlot('storage_limit'))
tbl.add(new Xtc('last_deposit_date', 'Last Deposit').noSort().renderSlot('last_deposit_date'))
// tbl.add(new Xtc('to_date', 'To').noSort().renderSlot('to_date'))
// tbl.add(new Xtc('months', 'Active Months'))
// tbl.add(new Xtc('action', 'Actions').noSort().renderSlot('actions'))
export default tbl

View File

@ -0,0 +1,12 @@
import XTbl, { Xtc } from '@/components/x-table/index'
const tbl = new XTbl('admin/list/subscription-transaction', 'Subscription Transactions')
tbl.add(new Xtc('amount', 'Amount'))
tbl.add(new Xtc('object_obj.title', 'Plan'))
tbl.add(new Xtc('created_at', 'Transaction Date').noSort().renderSlot('created_at'))
tbl.add(new Xtc('request_body.currency', 'Currency'))
tbl.add(new Xtc('request_body.merchant_code', 'Merchant Code'))
tbl.add(new Xtc('request_body.user_name', 'User'))
tbl.add(new Xtc('request_body.user_email', "User's Email"))
export default tbl

View File

@ -193,7 +193,6 @@ import { required, email } from '@validations'
import { togglePasswordVisibility } from '@core/mixins/ui/forms'
import store from '@/store/index'
import { login, setAccessToken, setUserData, getHomeRouteForLoggedInUser } from '@/auth/utils'
import { Log } from '@/plugins/core'
import ToastificationContent from '@core/components/toastification/ToastificationContent.vue'