mirror of
https://github.com/Dadechin/XRoomDashboardFront.git
synced 2025-07-02 00:04:35 +00:00
fix team page // fix meeting page API
This commit is contained in:
parent
8f96b6a571
commit
6ca289deb0
|
@ -1,66 +1,40 @@
|
|||
<template>
|
||||
<div class="buy-subscription-container">
|
||||
<!-- Subscription Title -->
|
||||
<div class="subscription-title">
|
||||
<h3 style="text-align: center; margin-bottom: 20px;">
|
||||
لطفا نوع اشتراک خود را انتخاب کنید
|
||||
</h3>
|
||||
<span>
|
||||
ما مدل مجوزدهی انعطافپذیری ارائه میدهد. شما میتوانید بهصورت ماهانه و به ازای هر کاربر پرداخت کنید. تعداد کاربران را میتوان فوراً افزایش داد، اما در صورت کاهش مجوزها، تغییرات در پایان دورهی صورتحساب اعمال خواهند شد.
|
||||
</span>
|
||||
<h3 style="text-align: center; margin-bottom: 20px;">لطفا نوع اشتراک خود را انتخاب کنید</h3>
|
||||
<span>مدل مجوزدهی انعطافپذیر با پرداخت ماهانه به ازای هر کاربر. تعداد کاربران را میتوان فوراً افزایش داد، اما کاهش مجوزها در پایان دورهی صورتحساب اعمال میشود.</span>
|
||||
</div>
|
||||
|
||||
<!-- User Count Selector -->
|
||||
<div class="user-count" style="text-align: start; margin: 3rem 0 2rem 0;">
|
||||
<label for="memberCount" style="margin-left: 10px;">تعداد کاربران : </label>
|
||||
<label for="memberCount" style="margin-left: 10px;">تعداد کاربران:</label>
|
||||
<select
|
||||
id="memberCount"
|
||||
:value="memberCount"
|
||||
@change="updateMemberCount($event)">
|
||||
@change="updateMemberCount($event)"
|
||||
>
|
||||
<option v-for="count in availableMemberOptions" :key="count" :value="count">
|
||||
{{ count }} کاربر
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Plan Cards -->
|
||||
<div style="display: flex; justify-content: space-between; flex-wrap: wrap;">
|
||||
<div class="plan-card">
|
||||
<div v-for="(plan, key) in plans" :key="key" class="plan-card">
|
||||
<div class="card-inner">
|
||||
<h4>هفتگی</h4>
|
||||
<h4>{{ plan.name }}</h4>
|
||||
<div class="card-price-title">
|
||||
<p>{{ (plans.weekly.price * memberCount).toLocaleString() }} تومان</p>
|
||||
<small>برای {{ memberCount }} کاربر، در هفته</small>
|
||||
<p>{{ (plan.price * memberCount).toLocaleString() }} تومان</p>
|
||||
<small>برای {{ memberCount }} کاربر، در {{ plan.name.toLowerCase() }}</small>
|
||||
</div>
|
||||
<button class="primary-button" @click="selectPlan('weekly')">
|
||||
انتخاب طرح اشتراک
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plan-card">
|
||||
<div class="card-inner">
|
||||
<h4>ماهانه</h4>
|
||||
<div class="card-price-title">
|
||||
<p>{{ (plans.monthly.price * memberCount).toLocaleString() }} تومان</p>
|
||||
<small>برای {{ memberCount }} کاربر، در ماه</small>
|
||||
</div>
|
||||
<button class="primary-button" @click="selectPlan('monthly')">
|
||||
انتخاب طرح اشتراک
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plan-card">
|
||||
<div class="card-inner">
|
||||
<h4>سالانه</h4>
|
||||
<div class="card-price-title">
|
||||
<p>{{ (plans.yearly.price * memberCount).toLocaleString() }} تومان</p>
|
||||
<small>برای {{ memberCount }} کاربر، در سال</small>
|
||||
</div>
|
||||
<button class="primary-button" @click="selectPlan('yearly')">
|
||||
انتخاب طرح اشتراک
|
||||
</button>
|
||||
<button class="primary-button" @click="selectPlan(key)">انتخاب طرح اشتراک</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- فاکتور -->
|
||||
<!-- Invoice -->
|
||||
<div
|
||||
v-if="selectedPlan"
|
||||
class="invoice-box"
|
||||
|
@ -75,15 +49,11 @@
|
|||
<span>مالیات (۹٪):</span>
|
||||
<span>{{ selectedPlan.tax.toLocaleString() }} تومان</span>
|
||||
</div>
|
||||
<div
|
||||
style="display: flex; justify-content: space-between; font-weight: bold; font-size: 16px; margin-bottom: 20px;"
|
||||
>
|
||||
<div style="display: flex; justify-content: space-between; font-weight: bold; font-size: 16px; margin-bottom: 20px;">
|
||||
<span>مبلغ قابل پرداخت:</span>
|
||||
<span>{{ selectedPlan.total.toLocaleString() }} تومان</span>
|
||||
</div>
|
||||
<button class="primary-button" style="width: 100%;" @click="pay">
|
||||
پرداخت
|
||||
</button>
|
||||
<button class="primary-button" style="width: 100%;" @click="pay">پرداخت</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -94,18 +64,11 @@ import axios from 'axios';
|
|||
export default {
|
||||
name: 'BuySubscription',
|
||||
props: {
|
||||
memberCount: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
},
|
||||
availableMemberOptions: {
|
||||
type: Array,
|
||||
default: () => [5, 10, 20, 100],
|
||||
},
|
||||
baseUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
memberCount: { type: Number, default: 5 },
|
||||
availableMemberOptions: { type: Array, default: () => [5, 10, 20, 100] },
|
||||
baseUrl: { type: String, required: true },
|
||||
hasActiveSubscription: { type: Boolean, default: false },
|
||||
hasExpiredSubscription: { type: Boolean, default: false }, // جدید
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -121,67 +84,60 @@ export default {
|
|||
updateMemberCount(event) {
|
||||
const newCount = Number(event.target.value);
|
||||
this.$emit('update:memberCount', newCount);
|
||||
if (this.selectedPlan) {
|
||||
this.selectPlan(
|
||||
this.selectedPlan.name === 'هفتگی' ? 'weekly' : this.selectedPlan.name === 'ماهانه' ? 'monthly' : 'yearly'
|
||||
);
|
||||
}
|
||||
if (this.selectedPlan) this.selectPlan(this.selectedPlan.name.toLowerCase());
|
||||
},
|
||||
selectPlan(planKey) {
|
||||
const plan = this.plans[planKey];
|
||||
if (!plan) return;
|
||||
|
||||
const base = plan.price * this.memberCount;
|
||||
const tax = Math.round(base * 0.09);
|
||||
|
||||
this.selectedPlan = {
|
||||
...plan,
|
||||
basePrice: base,
|
||||
tax,
|
||||
total: base + tax,
|
||||
};
|
||||
this.$emit('plan-selected', this.selectedPlan);
|
||||
this.selectedPlan = { ...plan, basePrice: base, tax, total: base + tax };
|
||||
},
|
||||
async pay() {
|
||||
if (!this.selectedPlan) {
|
||||
alert('لطفاً ابتدا یک طرح اشتراک انتخاب کنید.');
|
||||
if (this.hasActiveSubscription) {
|
||||
alert('شما اشتراک فعالی دارید و نمیتوانید اشتراک دیگری خریداری کنید.');
|
||||
return;
|
||||
}
|
||||
if (this.hasExpiredSubscription) {
|
||||
alert('شما یکبار اشتراک تهیه کردید و نمیتوانید دوباره اشتراک تهیه کنید.');
|
||||
return;
|
||||
}
|
||||
if (!this.selectedPlan) {
|
||||
alert('لطفاً یک طرح اشتراک انتخاب کنید.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const startTime = new Date().toISOString();
|
||||
let endTime;
|
||||
if (this.selectedPlan.name === 'هفتگی') {
|
||||
endTime = new Date(new Date().getTime() + 7 * 24 * 60 * 60 * 1000).toISOString();
|
||||
} else if (this.selectedPlan.name === 'ماهانه') {
|
||||
endTime = new Date(new Date().getTime() + 30 * 24 * 60 * 60 * 1000).toISOString();
|
||||
} else if (this.selectedPlan.name === 'سالانه') {
|
||||
endTime = new Date(new Date().getTime() + 365 * 24 * 60 * 60 * 1000).toISOString();
|
||||
}
|
||||
|
||||
const endTime = this.calculateEndTime(this.selectedPlan.name);
|
||||
const subscriptionData = {
|
||||
user_count: this.memberCount,
|
||||
license_number: `ABC-${Math.random().toString(36).substr(2, 6).toUpperCase()}-XYZ`,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
startTime,
|
||||
endTime,
|
||||
price: this.selectedPlan.total,
|
||||
};
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
await axios.post(`${this.baseUrl}/add_subscription/`, subscriptionData, {
|
||||
headers: {
|
||||
Authorization: `Token ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
if (!token) throw new Error('توکن احراز هویت یافت نشد.');
|
||||
const response = await axios.post(`${this.baseUrl}/add_subscription/`, subscriptionData, {
|
||||
headers: { Authorization: `Token ${token}`, 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
alert(`پرداخت با موفقیت انجام شد برای ${this.memberCount} کاربر`);
|
||||
this.$emit('payment-success', { subscriptionId: response.data.subscription_id });
|
||||
this.selectedPlan = null;
|
||||
this.$emit('payment-success');
|
||||
} catch (error) {
|
||||
console.error('Error registering subscription:', error);
|
||||
alert('خطا در ثبت اشتراک. لطفاً دوباره تلاش کنید.');
|
||||
}
|
||||
},
|
||||
calculateEndTime(planName) {
|
||||
const now = new Date();
|
||||
if (planName === 'هفتگی') {
|
||||
return new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString();
|
||||
} else if (planName === 'ماهانه') {
|
||||
return new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000).toISOString();
|
||||
} else {
|
||||
return new Date(now.getTime() + 365 * 24 * 60 * 60 * 1000).toISOString();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div v-if="isOpen && !isRoomSelectionOpen" class="modal-overlay" @click="closeModal">
|
||||
<div class="modal-content" @click.stop>
|
||||
<div class="modal-content" ref="modalContent" @click.stop>
|
||||
<div class="popUp-header">
|
||||
<h2>ایجاد جلسه جدید</h2>
|
||||
<button @click="closeModal">
|
||||
<button @click="closeModalByButton">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="35"
|
||||
|
@ -38,25 +38,16 @@
|
|||
<form @submit.prevent="handleSubmit">
|
||||
<div class="form-group">
|
||||
<label for="meetingTitle">نام جلسه</label>
|
||||
<input
|
||||
type="text"
|
||||
id="meetingTitle"
|
||||
v-model="form.title"
|
||||
required
|
||||
/>
|
||||
<input type="text" id="meetingTitle" v-model="form.title" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="meet-description">شرح جلسه</label>
|
||||
<textarea
|
||||
name="meet-description"
|
||||
id="meet-description"
|
||||
v-model="form.description"
|
||||
></textarea>
|
||||
<textarea name="meet-description" id="meet-description" v-model="form.description"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="meetingDate">روز</label>
|
||||
<div class="input-group">
|
||||
<span style="position: absolute; z-index: 1; top: 10px; right: 32%;">
|
||||
<span class="calendar-icon" style="position: absolute; z-index: 99; top: 10px; left: 65%;">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
|
@ -95,8 +86,8 @@
|
|||
:auto-submit="true"
|
||||
input-class="form-control"
|
||||
id="meetingDate"
|
||||
required
|
||||
style="border-radius: 0 8px 8px 0; text-align: center; position: relative;"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -286,31 +277,31 @@
|
|||
<div class="form-group">
|
||||
<label style="font-size: 19px; font-weight: 600;">اتاق جلسه</label>
|
||||
<div class="rooms-selecter">
|
||||
<span>{{ form.selectedRoom ? '0 اتاق انتخاب شده' : '0 اتاق انتخاب شده' }}</span>
|
||||
<button type="button" @click="openRoomSelection" style="cursor: pointer;">انتخاب اتاق جلسه</button>
|
||||
<span>{{ form.selectedRoom ? '1 اتاق انتخاب شده' : '0 اتاق انتخاب شده' }}</span>
|
||||
<button type="button" @click="openRoomSelection">انتخاب اتاق جلسه</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="participants-objects">
|
||||
<h2>شرکت کنندگان</h2>
|
||||
<p><span style="color: #101010;font-weight: 600;">کاربران</span> یا <span style="color: #101010;font-weight: 600;">مهمانان تیم</span> را با پر کردن شماره تلفن آنها دعوت کنید.</p>
|
||||
<p><span style="color: #101010; font-weight: 600;">کاربران</span> یا <span style="color: #101010; font-weight: 600;">مهمانان تیم</span> را از لیست زیر انتخاب کنید.</p>
|
||||
<span class="participants-guide">
|
||||
میتوانید به مجری اجازه بدهید تا ابزارهایی برای مدیریت این جلسه و همچنین ابزارهایی برای مدیریت مجوزها در طول جلسه به او بدهد.
|
||||
</span>
|
||||
</div>
|
||||
<div class="presenter">
|
||||
<div class="presenter">
|
||||
<div style="display: flex; align-items: center; height: 100%;">
|
||||
<div class="avatar-wrapper">
|
||||
<img class="user-avatar" :src="profileIcon" />
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<p class="user-name">{{ fullName }}</p>
|
||||
<span>{{ userPhone || 'شماره تلفن موجود نیست' }}</span>
|
||||
<span>{{ userPhone }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="presenter-role">{{ userRole }}</p>
|
||||
</div>
|
||||
<div class="presenter" v-for="participant in participants" :key="participant.phone">
|
||||
<div style="display: flex;align-items: center;height: 100%;">
|
||||
<div class="presenter" v-for="participant in participants" :key="participant.id">
|
||||
<div style="display: flex; align-items: center; height: 100%;">
|
||||
<div class="avatar-wrapper">
|
||||
<img class="user-avatar" :src="participant.profile_img || defaultProfileIcon" />
|
||||
</div>
|
||||
|
@ -320,7 +311,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<p class="presenter-role">{{ participant.role }}</p>
|
||||
<button @click="removeParticipant(participant.phone)">
|
||||
<button @click="removeParticipant(participant.id)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="35" height="35" viewBox="0 0 32 32" fill="none">
|
||||
<rect x="0.5" y="0.5" width="31" height="31" rx="7.5" fill="white"/>
|
||||
<rect x="0.5" y="0.5" width="31" height="31" rx="7.5" stroke="#E2DEE9"/>
|
||||
|
@ -330,21 +321,55 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="participantPhone">اضافه کردن شرکت کننده</label>
|
||||
<label for="participantInput">اضافه کردن شرکت کننده</label>
|
||||
<div class="participant-input">
|
||||
<input
|
||||
type="tel"
|
||||
id="participantPhone"
|
||||
v-model="newParticipantPhone"
|
||||
placeholder="لطفا شماره تلفن شرکت کننده را وارد کنید"
|
||||
@keyup.enter="addParticipant"
|
||||
/>
|
||||
<button type="button" @click="addParticipant">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M3.33203 8H12.6654" stroke="#3A57E8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 3.33325V12.6666" stroke="#3A57E8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<div class="custom-input" @click="toggleDropdown" ref="customInput">
|
||||
<span v-if="!selectedParticipantId">یک عضو تیم انتخاب کنید</span>
|
||||
<span v-else>{{ getParticipantName(selectedParticipantId) }}</span>
|
||||
<svg
|
||||
class="dropdown-icon"
|
||||
:class="{ active: isDropdownOpen }"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M5 7.5L10 12.5L15 7.5"
|
||||
stroke="#3A57E8"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="isDropdownOpen" class="dropdown-menu" ref="dropdownMenu">
|
||||
<div
|
||||
v-for="member in teamMembers"
|
||||
:key="member.user.id"
|
||||
class="dropdown-item"
|
||||
@click="selectParticipant(member.user.id)"
|
||||
>
|
||||
<div class="avatar-wrapper">
|
||||
<img
|
||||
:src="member.profile_img || defaultProfileIcon"
|
||||
class="user-avatar"
|
||||
alt="Profile"
|
||||
/>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
||||
<p class="user-name">{{ member.user.first_name }} {{ member.user.last_name }}</p>
|
||||
<span>{{ member.mobile_number }}</span>
|
||||
</div>
|
||||
<span class="user-role">{{ member.semat || 'بدون سمت' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!teamMembers.length" class="dropdown-item no-members">
|
||||
هیچ عضوی یافت نشد
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -353,7 +378,7 @@
|
|||
</span>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="button" class="cancel-button" @click="closeModal">بازگشت</button>
|
||||
<button type="button" class="cancel-button" @click="closeModalByButton">بازگشت</button>
|
||||
<button type="button" class="submit-button" @click="handleSubmit">ایجاد جلسه</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -369,21 +394,19 @@
|
|||
import VuePersianDatetimePicker from 'vue3-persian-datetime-picker';
|
||||
import moment from 'moment-jalaali';
|
||||
import RoomSelectionModal from './RoomSelectionModal.vue';
|
||||
import axios from 'axios';
|
||||
|
||||
const API_BASE_URL = 'http://my.xroomapp.com:8000';
|
||||
|
||||
export default {
|
||||
name: 'MeetingModal',
|
||||
components: {
|
||||
VuePersianDatetimePicker,
|
||||
RoomSelectionModal,
|
||||
},
|
||||
name: 'CreateMeetingModal',
|
||||
components: { VuePersianDatetimePicker, RoomSelectionModal },
|
||||
props: {
|
||||
isOpen: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isOpen: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultProfileIcon: 'https://models.readyplayer.me/681f59760bc631a87ad25172.png',
|
||||
form: {
|
||||
title: '',
|
||||
description: '',
|
||||
|
@ -393,27 +416,26 @@ export default {
|
|||
endHour: 18,
|
||||
endMinute: 0,
|
||||
selectedRoom: null,
|
||||
use_space: false,
|
||||
},
|
||||
participants: [],
|
||||
newParticipantPhone: '',
|
||||
defaultProfileIcon: 'https://c.animaapp.com/m9nvumalUMfQbN/img/frame.svg',
|
||||
error: null,
|
||||
teamMembers: [],
|
||||
selectedParticipantId: '',
|
||||
isDropdownOpen: false,
|
||||
isRoomSelectionOpen: false,
|
||||
error: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
customer() {
|
||||
// دریافت اطلاعات کاربر از localStorage با کلید customer
|
||||
return JSON.parse(localStorage.getItem('customer') || '{}');
|
||||
},
|
||||
fullName() {
|
||||
const user = JSON.parse(localStorage.getItem('user') || '{}');
|
||||
return user.first_name && user.last_name
|
||||
? `${user.first_name} ${user.last_name}`
|
||||
: 'کاربر مهمان';
|
||||
return user.first_name && user.last_name ? `${user.first_name} ${user.last_name}` : 'کاربر مهمان';
|
||||
},
|
||||
userPhone() {
|
||||
return 3;
|
||||
return this.customer.mobile_number || 'شماره تلفن موجود نیست';
|
||||
},
|
||||
userRole() {
|
||||
return this.customer.semat || 'مجری';
|
||||
|
@ -422,65 +444,122 @@ export default {
|
|||
return this.customer.profile_img || this.defaultProfileIcon;
|
||||
},
|
||||
userId() {
|
||||
const customer = JSON.parse(localStorage.getItem('customer') || '{}');
|
||||
return customer.id || null;
|
||||
}
|
||||
return this.customer.id || null;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isOpen(newVal) {
|
||||
document.body.style.overflow = newVal && !this.isRoomSelectionOpen ? 'hidden' : '';
|
||||
if (newVal) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else if (!this.isRoomSelectionOpen) {
|
||||
document.body.style.overflow = '';
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.modalContent) {
|
||||
this.$refs.modalContent.addEventListener('click', this.closeDropdownOnClick);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (this.$refs.modalContent) {
|
||||
this.$refs.modalContent.removeEventListener('click', this.closeDropdownOnClick);
|
||||
}
|
||||
}
|
||||
},
|
||||
isRoomSelectionOpen(newVal) {
|
||||
if (newVal) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else if (!this.isOpen) {
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
document.body.style.overflow = newVal ? 'hidden' : '';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.$refs.modalContent) {
|
||||
this.$refs.modalContent.addEventListener('click', this.closeDropdownOnClick);
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.$refs.modalContent) {
|
||||
this.$refs.modalContent.removeEventListener('click', this.closeDropdownOnClick);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
beforeDestroy() {
|
||||
document.body.style.overflow = '';
|
||||
async fetchTeamMembers() {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) throw new Error('توکن احراز هویت پیدا نشد');
|
||||
const response = await axios.get(`${API_BASE_URL}/get_all_team_members`, {
|
||||
headers: { Authorization: `Token ${token.trim()}` },
|
||||
});
|
||||
this.teamMembers = response.data.members.filter(
|
||||
(member) => member.user?.id && member.user.first_name && member.user.last_name
|
||||
);
|
||||
} catch (error) {
|
||||
if (error.response?.status === 403) {
|
||||
alert('لطفاً دوباره وارد شوید');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
this.error = 'خطا در بارگذاری لیست اعضای تیم';
|
||||
}
|
||||
},
|
||||
toggleDropdown(event) {
|
||||
this.isDropdownOpen = !this.isDropdownOpen;
|
||||
event.stopPropagation();
|
||||
},
|
||||
selectParticipant(id) {
|
||||
this.selectedParticipantId = id;
|
||||
this.isDropdownOpen = false;
|
||||
this.addParticipant();
|
||||
},
|
||||
closeDropdownOnClick(event) {
|
||||
if (this.isDropdownOpen && this.$refs.customInput && !this.$refs.customInput.contains(event.target)) {
|
||||
this.isDropdownOpen = false;
|
||||
}
|
||||
},
|
||||
openRoomSelection() {
|
||||
this.isRoomSelectionOpen = true;
|
||||
},
|
||||
handleRoomSelection(room) {
|
||||
this.form.selectedRoom = room;
|
||||
this.form.use_space = room.use_space;
|
||||
this.isRoomSelectionOpen = false;
|
||||
},
|
||||
getParticipantName(id) {
|
||||
const member = this.teamMembers.find((m) => m.user.id === id);
|
||||
return member ? `${member.user.first_name} ${member.user.last_name}` : '';
|
||||
},
|
||||
addParticipant() {
|
||||
if (!this.newParticipantPhone || !this.validatePhone(this.newParticipantPhone)) {
|
||||
this.error = 'لطفاً شماره تلفن معتبر وارد کنید (مثال: 09123456789)';
|
||||
if (!this.selectedParticipantId) {
|
||||
this.error = 'لطفاً یک عضو تیم انتخاب کنید.';
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.participants.some((p) => p.phone === this.newParticipantPhone) ||
|
||||
this.newParticipantPhone === this.userPhone
|
||||
) {
|
||||
this.error = 'این شماره تلفن قبلاً اضافه شده است';
|
||||
const selectedMember = this.teamMembers.find((member) => member.user.id === this.selectedParticipantId);
|
||||
if (!selectedMember) {
|
||||
this.error = 'کاربر انتخابشده یافت نشد.';
|
||||
return;
|
||||
}
|
||||
if (this.participants.some((p) => p.id === this.selectedParticipantId)) {
|
||||
this.error = 'این کاربر قبلاً اضافه شده است.';
|
||||
return;
|
||||
}
|
||||
if (this.selectedParticipantId === this.userId) {
|
||||
this.error = 'نمیتوانید خودتان را بهعنوان شرکتکننده اضافه کنید.';
|
||||
return;
|
||||
}
|
||||
this.participants.push({
|
||||
phone: this.newParticipantPhone,
|
||||
name: 'کاربر مهمان',
|
||||
role: 'شرکتکننده',
|
||||
profile_img: this.defaultProfileIcon,
|
||||
id: selectedMember.user.id,
|
||||
phone: selectedMember.mobile_number,
|
||||
name: `${selectedMember.user.first_name} ${selectedMember.user.last_name}`,
|
||||
role: selectedMember.semat || 'بدون سمت',
|
||||
profile_img: selectedMember.profile_img || this.defaultProfileIcon,
|
||||
});
|
||||
this.newParticipantPhone = '';
|
||||
this.selectedParticipantId = '';
|
||||
this.isDropdownOpen = false;
|
||||
this.error = null;
|
||||
},
|
||||
removeParticipant(phone) {
|
||||
this.participants = this.participants.filter((p) => p.phone !== phone);
|
||||
removeParticipant(id) {
|
||||
this.participants = this.participants.filter((p) => p.id !== id);
|
||||
},
|
||||
validatePhone(phone) {
|
||||
return /^09[0-9]{9}$/.test(phone);
|
||||
closeModal(event) {
|
||||
if (event && event.target.classList.contains('modal-overlay')) {
|
||||
this.$emit('close');
|
||||
this.resetForm();
|
||||
}
|
||||
},
|
||||
closeModal() {
|
||||
closeModalByButton() {
|
||||
this.$emit('close');
|
||||
this.resetForm();
|
||||
},
|
||||
|
@ -494,39 +573,30 @@ export default {
|
|||
endHour: 18,
|
||||
endMinute: 0,
|
||||
selectedRoom: null,
|
||||
use_space: false,
|
||||
};
|
||||
this.participants = [];
|
||||
this.newParticipantPhone = '';
|
||||
this.selectedParticipantId = '';
|
||||
this.isDropdownOpen = false;
|
||||
this.error = null;
|
||||
this.isRoomSelectionOpen = false;
|
||||
},
|
||||
incrementTime(field) {
|
||||
if (field === 'startHour' && this.form.startHour < 23) {
|
||||
this.form.startHour++;
|
||||
} else if (field === 'startMinute' && this.form.startMinute < 59) {
|
||||
this.form.startMinute++;
|
||||
} else if (field === 'endHour' && this.form.endHour < 23) {
|
||||
this.form.endHour++;
|
||||
} else if (field === 'endMinute' && this.form.endMinute < 59) {
|
||||
this.form.endMinute++;
|
||||
}
|
||||
const limits = { startHour: 23, startMinute: 59, endHour: 23, endMinute: 59 };
|
||||
if (this.form[field] < limits[field]) this.form[field]++;
|
||||
},
|
||||
decrementTime(field) {
|
||||
if (field === 'startHour' && this.form.startHour > 0) {
|
||||
this.form.startHour--;
|
||||
} else if (field === 'startMinute' && this.form.startMinute > 0) {
|
||||
this.form.startMinute--;
|
||||
} else if (field === 'endHour' && this.form.endHour > 0) {
|
||||
this.form.endHour--;
|
||||
} else if (field === 'endMinute' && this.form.endMinute > 0) {
|
||||
this.form.endMinute--;
|
||||
}
|
||||
if (this.form[field] > 0) this.form[field]--;
|
||||
},
|
||||
async handleSubmit() {
|
||||
if (!this.form.title || !this.form.date) {
|
||||
this.error = 'لطفاً نام جلسه و تاریخ را وارد کنید.';
|
||||
return;
|
||||
}
|
||||
if (!this.form.selectedRoom) {
|
||||
this.error = 'لطفاً یک اتاق برای جلسه انتخاب کنید.';
|
||||
return;
|
||||
}
|
||||
const momentDate = moment(this.form.date, 'jYYYY/jMM/jDD');
|
||||
if (!momentDate.isValid()) {
|
||||
this.error = 'تاریخ وارد شده معتبر نیست.';
|
||||
|
@ -540,43 +610,162 @@ export default {
|
|||
}
|
||||
const startDateTime = momentDate
|
||||
.clone()
|
||||
.set({
|
||||
hour: this.form.startHour,
|
||||
minute: this.form.startMinute,
|
||||
second: 0,
|
||||
})
|
||||
.set({ hour: this.form.startHour, minute: this.form.startMinute, second: 0 })
|
||||
.toISOString();
|
||||
|
||||
try {
|
||||
const userIds = [
|
||||
...(this.userPhone ? [this.userPhone] : []),
|
||||
...this.participants.map((p) => p.phone),
|
||||
];
|
||||
|
||||
const meetingData = {
|
||||
name: this.form.title,
|
||||
description: this.form.description,
|
||||
date_time: startDateTime,
|
||||
space: this.form.selectedRoom ? this.form.selectedRoom.id : null,
|
||||
space: this.form.selectedRoom.id,
|
||||
asset_bundle: 1,
|
||||
use_space: !!this.form.selectedRoom,
|
||||
user_ids: userIds,
|
||||
use_space: this.form.use_space,
|
||||
user_ids: [this.userId, ...this.participants.map((p) => p.id)],
|
||||
};
|
||||
|
||||
console.log('دادههای ارسالی به API:', JSON.stringify(meetingData, null, 2));
|
||||
|
||||
this.$emit('create-meeting', meetingData);
|
||||
this.closeModal();
|
||||
this.closeModalByButton();
|
||||
} catch (error) {
|
||||
this.error = `خطا در آمادهسازی دادهها: ${error.message}`;
|
||||
console.error('خطا در handleSubmit:', error);
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.fetchTeamMembers();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.participant-input {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.custom-input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #E2DEE9;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
transition: border-color 0.3s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.custom-input:hover {
|
||||
border-color: #3A57E8;
|
||||
}
|
||||
|
||||
.dropdown-icon {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.custom-input.active .dropdown-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
border: 1px solid #E2DEE9;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.dropdown-item .avatar-wrapper {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.dropdown-item .user-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.dropdown-item .user-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding-left: 2.5rem;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
|
||||
.dropdown-item .user-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #101010;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: clip;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.dropdown-item .user-info span {
|
||||
font-size: 14px;
|
||||
color: #718096;
|
||||
width: 75px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
.dropdown-item .user-role {
|
||||
font-size: 12px;
|
||||
color: #3A57E8;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.no-members {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
color: #718096;
|
||||
}
|
||||
|
||||
.participant-input button {
|
||||
margin-left: 10px;
|
||||
padding: 10px;
|
||||
background: #3A57E8;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* */
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
@ -876,6 +1065,10 @@ export default {
|
|||
font-size: 18px;
|
||||
color: #101010;
|
||||
font-weight: 600;
|
||||
width: 160px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
.presenter button {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="tab-content">
|
||||
<!-- Access Container -->
|
||||
<div class="access-container">
|
||||
<!-- Header Section -->
|
||||
<div class="access-header">
|
||||
<div class="header-content">
|
||||
<img :src="require('@/assets/img/lock Icon.png')" alt="lock" class="lock-icon" />
|
||||
|
@ -16,90 +16,83 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Info Cards Section -->
|
||||
<div class="info-cards" v-if="isLoading">
|
||||
<div class="loading">در حال بارگذاری...</div>
|
||||
</div>
|
||||
<div class="info-cards" v-else-if="error">
|
||||
<div class="error">{{ error }} <button @click="retryFetch">تلاش مجدد</button></div>
|
||||
</div>
|
||||
<div class="info-cards" v-else>
|
||||
<!-- Billing Info Card -->
|
||||
<div class="info-card">
|
||||
<div class="card-content">
|
||||
<h4>{{ translations.billing.title }}</h4>
|
||||
<div class="billing-info">
|
||||
<p class="billing-address">{{ billingInfo.address }}</p>
|
||||
<p class="billing-phoneNum">
|
||||
{{ translations.billing.phone }}: <span>{{ billingInfo.phone }}</span>
|
||||
<!-- Info Cards -->
|
||||
<div class="info-cards">
|
||||
<div v-if="isLoading" class="loading">در حال بارگذاری...</div>
|
||||
<div v-else-if="error" class="error">
|
||||
{{ error }} <button @click="retryFetch">تلاش مجدد</button>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="info-card">
|
||||
<div class="card-content">
|
||||
<h4>{{ translations.billing.title }}</h4>
|
||||
<div class="billing-info">
|
||||
<p class="billing-address">{{ billingInfo.address }}</p>
|
||||
<p class="billing-phoneNum">
|
||||
{{ translations.billing.phone }}: <span>{{ billingInfo.phone }}</span>
|
||||
</p>
|
||||
<p class="billing-email">
|
||||
{{ translations.billing.email }}: <span>{{ billingInfo.email }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="secondary-button" @click="openBillingModal">
|
||||
{{ translations.billing.editButton }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="card-content">
|
||||
<h4>{{ translations.memberships.title }}</h4>
|
||||
<p class="memberships">
|
||||
{{ hasActiveSubscription ? translations.memberships.active : translations.memberships.inactive }}
|
||||
</p>
|
||||
<p class="billing-email">
|
||||
{{ translations.billing.email }}: <span>{{ billingInfo.email }}</span>
|
||||
</div>
|
||||
<button class="secondary-button" @click="navigateToSubscription">
|
||||
{{ translations.memberships.manageButton }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="card-content">
|
||||
<h4>{{ translations.payment.title }}</h4>
|
||||
<p class="payment-method">
|
||||
{{ paymentMethod || translations.payment.noMethod }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="secondary-button" @click="openBillingModal">
|
||||
{{ translations.billing.editButton }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Memberships Card -->
|
||||
<div class="info-card">
|
||||
<div class="card-content">
|
||||
<h4>{{ translations.memberships.title }}</h4>
|
||||
<p class="memberships">
|
||||
{{ hasActiveSubscription ? translations.memberships.active : translations.memberships.inactive }}
|
||||
</p>
|
||||
</div>
|
||||
<button class="secondary-button" @click="manageMemberships">
|
||||
{{ translations.memberships.manageButton }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Payment Method Card -->
|
||||
<div class="info-card">
|
||||
<div class="card-content">
|
||||
<h4>{{ translations.payment.title }}</h4>
|
||||
<p class="payment-method">
|
||||
{{ paymentMethod || translations.payment.noMethod }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Team Subscription Card -->
|
||||
<div class="info-card">
|
||||
<div class="card-content">
|
||||
<h4>{{ translations.subscription.title }}</h4>
|
||||
<div class="subscription-info" v-if="hasRemainingCapacity">
|
||||
<p class="subscription-all">
|
||||
{{ translations.subscription.total }}: <span>{{ subscriptionCount }} {{ translations.subscription.users }}</span>
|
||||
</p>
|
||||
<p class="subscription-remainder">
|
||||
{{ translations.subscription.remaining }}: <span>{{ remainingCapacity }} {{ translations.subscription.users }}</span>
|
||||
</p>
|
||||
<p class="subscription-added">
|
||||
{{ translations.subscription.added }}: <span>{{ teamMemberCapacity }} {{ translations.subscription.users }}</span>
|
||||
<div class="info-card">
|
||||
<div class="card-content">
|
||||
<h4>{{ translations.subscription.title }}</h4>
|
||||
<div v-if="hasRemainingCapacity" class="subscription-info">
|
||||
<p class="subscription-all">
|
||||
{{ translations.subscription.total }}: <span>{{ subscriptionCount }} {{ translations.subscription.users }}</span>
|
||||
</p>
|
||||
<p class="subscription-remainder">
|
||||
{{ translations.subscription.remaining }}: <span>{{ remainingCapacity }} {{ translations.subscription.users }}</span>
|
||||
</p>
|
||||
<p class="subscription-added">
|
||||
{{ translations.subscription.added }}: <span>{{ teamMemberCapacity }} {{ translations.subscription.users }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<p v-else class="invalid-subscription">
|
||||
{{ translations.subscription.noActive }}
|
||||
</p>
|
||||
</div>
|
||||
<p class="invalid-subscription" v-else>
|
||||
{{ translations.subscription.noActive }}
|
||||
</p>
|
||||
<button
|
||||
:class="hasRemainingCapacity ? 'disable-button' : 'secondary-button'"
|
||||
:disabled="hasRemainingCapacity"
|
||||
@click="navigateToSubscription"
|
||||
>
|
||||
{{ hasRemainingCapacity ? translations.subscription.activeButton : translations.subscription.buyButton }}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
:class="hasRemainingCapacity ? 'disable-button' : 'secondary-button'"
|
||||
:disabled="hasRemainingCapacity"
|
||||
@click="navigateToSubscription"
|
||||
>
|
||||
{{ hasRemainingCapacity ? translations.subscription.activeButton : translations.subscription.buyButton }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Billing Modal -->
|
||||
<!-- Edit Billing Modal -->
|
||||
<EditBillingModal
|
||||
:isVisible="isBillingModalVisible"
|
||||
:is-visible="isBillingModalVisible"
|
||||
@close="closeBillingModal"
|
||||
@update:billingInfo="updateBillingInfo"
|
||||
@update:billing-info="updateBillingInfo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -110,24 +103,11 @@ import EditBillingModal from '@/components/EditBillingModal.vue';
|
|||
|
||||
export default {
|
||||
name: 'Membership',
|
||||
components: {
|
||||
EditBillingModal,
|
||||
},
|
||||
components: { EditBillingModal },
|
||||
props: {
|
||||
subscriptionCount: {
|
||||
type: Number,
|
||||
required: true,
|
||||
validator: (value) => value >= 0,
|
||||
},
|
||||
teamMemberCapacity: {
|
||||
type: Number,
|
||||
required: true,
|
||||
validator: (value) => value >= 0,
|
||||
},
|
||||
isBillingModalVisible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
subscriptionCount: { type: Number, required: true, validator: value => value >= 0 },
|
||||
teamMemberCapacity: { type: Number, required: true, validator: value => value >= 0 },
|
||||
isBillingModalVisible: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -141,7 +121,7 @@ export default {
|
|||
memberships: {
|
||||
title: 'عضویتها',
|
||||
active: 'اشتراک فعال است',
|
||||
inactive: 'هنوز مجوزی فعال نیست. کاربران شما نمیتوانند از XRoom با واترمارک استفاده کنند.',
|
||||
inactive: 'هنوز مجوزی فعال نیست. کاربران شما نمیتوانند از XRoom بدون واترمارک استفاده کنند.',
|
||||
manageButton: 'مدیریت عضویتها',
|
||||
},
|
||||
payment: {
|
||||
|
@ -152,15 +132,13 @@ export default {
|
|||
title: 'وضعیت اشتراک تیم',
|
||||
total: 'ظرفیت کل تیم',
|
||||
remaining: 'ظرفیت باقیمانده',
|
||||
added: 'کاربران اضافه کرده',
|
||||
added: 'کاربران اضافه شده',
|
||||
users: 'کاربر',
|
||||
noActive: 'شما اشتراک فعالی ندارین، لطفا اشتراک جدیدی خریداری نمایید.',
|
||||
noActive: 'شما اشتراک فعالی ندارید، لطفاً اشتراک جدیدی خریداری کنید.',
|
||||
activeButton: 'اشتراک فعال دارید',
|
||||
buyButton: 'خرید اشتراک جدید',
|
||||
},
|
||||
error: {
|
||||
fetchFailed: 'خطا در دریافت اطلاعات. لطفاً دوباره تلاش کنید.',
|
||||
},
|
||||
error: { fetchFailed: 'خطا در دریافت اطلاعات. لطفاً دوباره تلاش کنید.' },
|
||||
},
|
||||
billingInfo: {
|
||||
address: 'اصفهان، خیابان وحید، نبش خیابان حسین آباد، مجتمع عسگری ۳، واحد ۳ ۸۱۷۵۹۴۹۹۹۱',
|
||||
|
@ -182,19 +160,23 @@ export default {
|
|||
},
|
||||
},
|
||||
created() {
|
||||
this.simulateFetch();
|
||||
this.fetchData();
|
||||
},
|
||||
methods: {
|
||||
simulateFetch() {
|
||||
// شبیهسازی دریافت دادهها
|
||||
async fetchData() {
|
||||
this.isLoading = true;
|
||||
setTimeout(() => {
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
this.hasActiveSubscription = this.subscriptionCount > 0;
|
||||
} catch {
|
||||
this.error = this.translations.error.fetchFailed;
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
retryFetch() {
|
||||
this.error = null;
|
||||
this.simulateFetch();
|
||||
this.fetchData();
|
||||
},
|
||||
navigateToSubscription() {
|
||||
this.$emit('change-tab', 'buy-subscription');
|
||||
|
@ -212,6 +194,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
.access-container {
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<h2>فضاها</h2>
|
||||
<span>یک اتاق برای این جلسه انتخاب کنید</span>
|
||||
</div>
|
||||
<div class="rooms-list" v-if="rooms.length > 0">
|
||||
<div class="rooms-list" v-if="rooms.length">
|
||||
<div
|
||||
v-for="room in rooms"
|
||||
:key="room.id"
|
||||
|
@ -46,8 +46,8 @@
|
|||
:src="room.image"
|
||||
alt="Room Image"
|
||||
class="room-image"
|
||||
width="120px"
|
||||
height="120px"
|
||||
width="120"
|
||||
height="120"
|
||||
@error="room.image = 'https://via.placeholder.com/150'"
|
||||
/>
|
||||
<div class="room-details" style="margin-right: 10px;">
|
||||
|
@ -168,12 +168,12 @@
|
|||
<div v-else>
|
||||
<span>هیچ فضایی یافت نشد.</span>
|
||||
</div>
|
||||
<div v-if="temporaryRooms.length > 0">
|
||||
<div v-if="temporaryRooms.length">
|
||||
<div class="popUp-title">
|
||||
<h2>اتاقهای موقت</h2>
|
||||
<span>اتاقهای موقت ایجادشده برای این جلسه</span>
|
||||
</div>
|
||||
<div class="temporary-rooms-list">
|
||||
<div class="rooms-list">
|
||||
<div
|
||||
v-for="room in temporaryRooms"
|
||||
:key="room.id"
|
||||
|
@ -185,8 +185,8 @@
|
|||
:src="room.image"
|
||||
alt="Room Image"
|
||||
class="room-image"
|
||||
width="120px"
|
||||
height="120px"
|
||||
width="120"
|
||||
height="120"
|
||||
@error="room.image = 'https://via.placeholder.com/150'"
|
||||
/>
|
||||
<div class="room-details" style="margin-right: 10px;">
|
||||
|
@ -305,35 +305,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popUp-title">
|
||||
<h2>ایجاد اتاق موقت</h2>
|
||||
<span>اتاق موقت را فقط برای این جلسه ایجاد کنید</span>
|
||||
</div>
|
||||
<div class="temporary-room-form">
|
||||
<form @submit.prevent="createTemporaryRoom">
|
||||
<div class="form-group">
|
||||
<label for="tempRoomName">نام اتاق</label>
|
||||
<input
|
||||
type="text"
|
||||
id="tempRoomName"
|
||||
v-model="newTempRoom.name"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="tempRoomImage">تصویر اتاق</label>
|
||||
<input type="file" id="tempRoomImage" accept="image/*" @change="handleImageUpload" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="tempRoomCapacity">ظرفیت</label>
|
||||
<input v-model.number="newTempRoom.capacity" type="number" id="tempRoomCapacity" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="tempRoomType">نوع</label>
|
||||
<input v-model="newTempRoom.type" id="tempRoomType" required />
|
||||
</div>
|
||||
<button type="submit" class="create-room">ایجاد اتاق</button>
|
||||
</form>
|
||||
<div v-else>
|
||||
<span>هیچ اتاق موقتی یافت نشد.</span>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="button" class="cancel-button" @click="cancel">لغو</button>
|
||||
|
@ -346,72 +319,81 @@
|
|||
<script>
|
||||
import axios from 'axios';
|
||||
|
||||
const API_BASE_URL = 'http://my.xroomapp.com:8000';
|
||||
const DEFAULT_IMAGE = 'https://via.placeholder.com/150';
|
||||
|
||||
export default {
|
||||
name: 'RoomSelectionModal',
|
||||
props: {
|
||||
isOpen: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isOpen: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rooms: [],
|
||||
temporaryRooms: [],
|
||||
selectedRoom: null,
|
||||
newTempRoom: {
|
||||
name: '',
|
||||
capacity: 0,
|
||||
type: '',
|
||||
image: null,
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetchSpaces();
|
||||
this.fetchTemporaryRooms();
|
||||
},
|
||||
methods: {
|
||||
async fetchSpaces() {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
this.error = 'توکن احراز هویت پیدا نشد';
|
||||
console.error('توکن احراز هویت پیدا نشد');
|
||||
return;
|
||||
}
|
||||
if (!token) throw new Error('توکن احراز هویت پیدا نشد');
|
||||
|
||||
const response = await axios.get('http://my.xroomapp.com:8000/get_space', {
|
||||
headers: {
|
||||
Authorization: `Token ${token.trim()}`,
|
||||
},
|
||||
const response = await axios.get(`${API_BASE_URL}/get_space`, {
|
||||
headers: { Authorization: `Token ${token.trim()}` },
|
||||
});
|
||||
|
||||
console.log('فضاها:', response.data.spaces);
|
||||
this.rooms = response.data.spaces.map((space) => {
|
||||
const imageUrl = space.assetBundleRoomId?.img
|
||||
? `http://my.xroomapp.com:8000${space.assetBundleRoomId.img}`
|
||||
: 'https://via.placeholder.com/150';
|
||||
return {
|
||||
id: space.id,
|
||||
image: imageUrl,
|
||||
name: space.name,
|
||||
capacity: space.capacity,
|
||||
type: space.description || 'فضا',
|
||||
isTemporary: false,
|
||||
};
|
||||
});
|
||||
this.rooms = response.data.spaces.map((space) => ({
|
||||
id: space.id,
|
||||
image: space.assetBundleRoomId?.img
|
||||
? `${API_BASE_URL}${space.assetBundleRoomId.img}`
|
||||
: DEFAULT_IMAGE,
|
||||
name: space.name,
|
||||
capacity: space.capacity,
|
||||
type: space.description || 'فضا',
|
||||
isTemporary: false,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('خطا در دریافت فضاها:', error);
|
||||
this.error = 'خطا در بارگذاری لیست اتاقها';
|
||||
if (error.response && error.response.status === 403) {
|
||||
if (error.response?.status === 403) {
|
||||
alert('لطفاً دوباره وارد شوید');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
this.error = 'خطا در بارگذاری لیست اتاقها';
|
||||
}
|
||||
},
|
||||
async fetchTemporaryRooms() {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) throw new Error('توکن احراز هویت پیدا نشد');
|
||||
|
||||
const response = await axios.get(`${API_BASE_URL}/get_assigned_assetbundle_rooms`, {
|
||||
headers: { Authorization: `Token ${token.trim()}` },
|
||||
});
|
||||
|
||||
this.temporaryRooms = response.data.assetbundle_rooms.map((room) => ({
|
||||
id: room.id,
|
||||
image: room.img ? `${API_BASE_URL}${room.img}` : DEFAULT_IMAGE,
|
||||
name: room.name,
|
||||
capacity: room.maxPerson,
|
||||
type: room.description || 'اتاق موقت',
|
||||
isTemporary: true,
|
||||
}));
|
||||
} catch (error) {
|
||||
if (error.response?.status === 403) {
|
||||
alert('لطفاً دوباره وارد شوید');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
this.error = 'خطا در بارگذاری لیست اتاقهای موقت';
|
||||
}
|
||||
},
|
||||
selectRoom(roomId) {
|
||||
this.selectedRoom = roomId;
|
||||
this.selectedRoom = this.selectedRoom === roomId ? null : roomId;
|
||||
},
|
||||
cancel() {
|
||||
this.$emit('close');
|
||||
|
@ -425,27 +407,13 @@ export default {
|
|||
const selectedRoomDetails = [...this.rooms, ...this.temporaryRooms].find(
|
||||
(room) => room.id === this.selectedRoom
|
||||
);
|
||||
this.$emit('submit-room', selectedRoomDetails);
|
||||
this.$emit('submit-room', {
|
||||
...selectedRoomDetails,
|
||||
id: selectedRoomDetails.isTemporary ? 12 : selectedRoomDetails.id,
|
||||
use_space: !selectedRoomDetails.isTemporary,
|
||||
});
|
||||
this.selectedRoom = null;
|
||||
},
|
||||
handleImageUpload(event) {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
this.newTempRoom.image = URL.createObjectURL(file);
|
||||
}
|
||||
},
|
||||
createTemporaryRoom() {
|
||||
const newRoom = {
|
||||
id: this.rooms.length + this.temporaryRooms.length + 1,
|
||||
image: this.newTempRoom.image || 'https://via.placeholder.com/150',
|
||||
name: this.newTempRoom.name,
|
||||
capacity: this.newTempRoom.capacity,
|
||||
type: this.newTempRoom.type,
|
||||
isTemporary: true,
|
||||
};
|
||||
this.temporaryRooms.push(newRoom);
|
||||
this.newTempRoom = { name: '', capacity: 0, type: '', image: null };
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- Overlay for mobile when sidebar is open -->
|
||||
<div class="overlay" v-if="isOpen && isMobile" @click="$emit('close')"></div>
|
||||
<div class="overlay" :class="{ active: isOpen && isMobile }" v-if="isOpen && isMobile" @click="$emit('close')"></div>
|
||||
|
||||
<div class="sidebar" :class="{ 'open': isOpen }">
|
||||
|
||||
|
@ -62,16 +62,14 @@
|
|||
<div class="text-wrapper">پشتیبانی</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Close Button for mobile -->
|
||||
<button class="close-button" v-if="isMobile" @click="$emit('close')">
|
||||
<button class="close-button" v-show="isOpen" v-if="isMobile" @click="$emit('close')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -134,6 +132,8 @@ export default {
|
|||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
transform: transform translateX(100%);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.sidebar::-webkit-scrollbar {
|
||||
|
@ -146,6 +146,7 @@ export default {
|
|||
}
|
||||
|
||||
.sidebar.open {
|
||||
transform: translateX(0);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3rem;
|
||||
|
@ -159,6 +160,14 @@ export default {
|
|||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.overlay.active {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
|
@ -171,6 +180,7 @@ export default {
|
|||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
padding-top: 7px;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.close-button svg {
|
||||
|
@ -337,8 +347,12 @@ export default {
|
|||
.sidebar {
|
||||
width: 250px;
|
||||
padding: 1rem 1rem 1rem 0.5rem;
|
||||
display: none;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.sidebar.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
|
@ -366,8 +380,11 @@ export default {
|
|||
.sidebar {
|
||||
width: 22rem;
|
||||
padding: 1rem 1rem 1rem 0.5rem;
|
||||
display: none;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.sidebar.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
|
@ -397,6 +414,8 @@ export default {
|
|||
padding: 30px 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: translateX(0);
|
||||
|
||||
}
|
||||
|
||||
.close-button {
|
||||
|
@ -440,8 +459,7 @@ export default {
|
|||
.sidebar {
|
||||
width: 360px;
|
||||
padding: 30px 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.close-button {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<template>
|
||||
<div class="tab-content">
|
||||
<!-- Team Logo Section -->
|
||||
<div class="team-logo">
|
||||
<div class="card-title">
|
||||
<h2>لوگوی تیم</h2>
|
||||
<p>این لوگو در اتاقهای شما استفاده خواهد شد. توصیه میکنیم از یک تصویر شفاف با نسبت تصویر 2:1 استفاده کنید.</p>
|
||||
<p>این لوگو در اتاقهای شما نمایش داده میشود. توصیه میشود از تصویر شفاف با نسبت 2:1 استفاده کنید.</p>
|
||||
</div>
|
||||
<div class="logo-info">
|
||||
<img :src="teamLogo || require('@/assets/img/team-logo.jpg')" alt="team logo" />
|
||||
|
@ -16,34 +17,27 @@
|
|||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
>
|
||||
<g clip-path="url(#clip0_312_7133)">
|
||||
<path
|
||||
d="M2.66602 11.3333V12.6666C2.66602 13.0202 2.80649 13.3593 3.05654 13.6094C3.30659 13.8594 3.64573 13.9999 3.99935 13.9999H11.9993C12.353 13.9999 12.6921 13.8594 12.9422 13.6094C13.1922 13.3593 13.3327 13.0202 13.3327 12.6666V11.3333"
|
||||
stroke="white"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M4.66602 6.00008L7.99935 2.66675L11.3327 6.00008"
|
||||
stroke="white"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M8 2.66675V10.6667"
|
||||
stroke="white"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_312_7133">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<path
|
||||
d="M2.66602 11.3333V12.6666C2.66602 13.0202 2.80649 13.3593 3.05654 13.6094C3.30659 13.8594 3.64573 13.9999 3.99935 13.9999H11.9993C12.353 13.9999 12.6921 13.8594 12.9422 13.6094C13.1922 13.3593 13.3327 13.0202 13.3327 12.6666V11.3333"
|
||||
stroke="white"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M4.66602 6.00008L7.99935 2.66675L11.3327 6.00008"
|
||||
stroke="white"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M8 2.66675V10.6667"
|
||||
stroke="white"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span>آپلود</span>
|
||||
|
@ -60,7 +54,7 @@
|
|||
<div class="logo-sample">
|
||||
<div class="logo-sample-title">
|
||||
<h2>نمونه</h2>
|
||||
<span>به این ترتیب لوگوی تیم شما در اتاقهای شما به نظر میرسد.</span>
|
||||
<span>نمایش لوگوی تیم شما در اتاقها به این شکل خواهد بود.</span>
|
||||
</div>
|
||||
<div class="sample-logos">
|
||||
<img
|
||||
|
@ -72,6 +66,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Team Info Section -->
|
||||
<div class="team-info">
|
||||
<div class="card-title">
|
||||
<h2>جزئیات تیم</h2>
|
||||
|
@ -85,15 +81,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="company_name">نام شرکت</label>
|
||||
<input
|
||||
id="company_name"
|
||||
type="text"
|
||||
v-model="form.companyName"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="type_activity">نوع فعالیت شرکت</label>
|
||||
<label for="type_activity">نوع فعالیت</label>
|
||||
<input
|
||||
id="type_activity"
|
||||
type="text"
|
||||
|
@ -116,7 +104,6 @@ export default {
|
|||
return {
|
||||
form: {
|
||||
teamName: '',
|
||||
companyName: '',
|
||||
activityType: '',
|
||||
teamId: null,
|
||||
},
|
||||
|
@ -131,93 +118,75 @@ export default {
|
|||
};
|
||||
},
|
||||
created() {
|
||||
// دریافت اطلاعات تیم در زمان ایجاد کامپوننت
|
||||
this.fetchTeamData();
|
||||
},
|
||||
methods: {
|
||||
/* دریافت اطلاعات تیم از API */
|
||||
async fetchTeamData() {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) throw new Error('توکن احراز هویت یافت نشد.');
|
||||
const response = await axios.get(`${this.baseUrl}/get_team`, {
|
||||
headers: {
|
||||
Authorization: `Token ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
headers: { Authorization: `Token ${token}`, 'Content-Type': 'application/json' },
|
||||
});
|
||||
const team = response.data.teams[0];
|
||||
if (team) {
|
||||
this.form.teamName = team.name || '';
|
||||
this.form.activityType = team.description || '';
|
||||
this.form.teamId = team.id;
|
||||
this.teamLogo = team.logo ? `${this.baseUrl}${team.logo}` : null;
|
||||
} else {
|
||||
alert('هیچ اطلاعاتی برای تیم یافت نشد.');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('خطا در بارگذاری اطلاعات تیم. لطفاً دوباره تلاش کنید.');
|
||||
} catch {
|
||||
alert('خطا در بارگذاری اطلاعات تیم.');
|
||||
}
|
||||
},
|
||||
|
||||
handleLogoUpload(event) {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
this.teamLogo = URL.createObjectURL(file);
|
||||
this.uploadedLogoFile = file;
|
||||
this.uploadedLogoFile = event.target.files[0];
|
||||
if (this.uploadedLogoFile) {
|
||||
this.teamLogo = URL.createObjectURL(this.uploadedLogoFile);
|
||||
}
|
||||
},
|
||||
|
||||
/* ارسال فرم برای بهروزرسانی اطلاعات تیم */
|
||||
async submitForm() {
|
||||
const hasFormData = this.form.teamName || this.form.companyName || this.form.activityType;
|
||||
const hasLogo = !!this.uploadedLogoFile;
|
||||
|
||||
if (!hasFormData && !hasLogo) {
|
||||
alert('لطفاً حداقل یک فیلد یا لوگو را وارد کنید.');
|
||||
if (!this.form.teamName && !this.form.activityType && !this.uploadedLogoFile) {
|
||||
alert('لطفاً حداقل یک فیلد یا لوگو وارد کنید.');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
if (this.form.teamName) formData.append('name', this.form.teamName);
|
||||
if (this.form.companyName) formData.append('company_name', this.form.companyName);
|
||||
if (this.form.activityType) formData.append('description', this.form.activityType);
|
||||
if (this.uploadedLogoFile) formData.append('logo', this.uploadedLogoFile);
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
if (this.form.teamName) formData.append('name', this.form.teamName);
|
||||
if (this.form.activityType) formData.append('description', this.form.activityType);
|
||||
if (this.uploadedLogoFile) formData.append('logo', this.uploadedLogoFile);
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) throw new Error('توکن احراز هویت یافت نشد.');
|
||||
await axios.patch(`${this.baseUrl}/update_team/${this.form.teamId}/`, formData, {
|
||||
headers: {
|
||||
Authorization: `Token ${token}`,
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
headers: { Authorization: `Token ${token}`, 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
this.$emit('update:teamData', {
|
||||
this.$emit('update:team-data', {
|
||||
teamName: this.form.teamName,
|
||||
companyName: this.form.companyName,
|
||||
activityType: this.form.activityType,
|
||||
teamLogo: this.uploadedLogoFile,
|
||||
});
|
||||
alert('اطلاعات تیم با موفقیت بهروزرسانی شد');
|
||||
|
||||
// ریست فرم و لوگو
|
||||
this.form.teamName = '';
|
||||
this.form.companyName = '';
|
||||
this.form.activityType = '';
|
||||
this.teamLogo = null;
|
||||
this.uploadedLogoFile = null;
|
||||
const fileInput = this.$refs.fileUpload;
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
}
|
||||
alert('اطلاعات تیم با موفقیت بهروزرسانی شد.');
|
||||
this.resetForm();
|
||||
await this.fetchTeamData();
|
||||
} catch (error) {
|
||||
alert('خطا در بهروزرسانی اطلاعات تیم. لطفاً دوباره تلاش کنید.');
|
||||
} catch {
|
||||
alert('خطا در بهروزرسانی اطلاعات تیم.');
|
||||
}
|
||||
},
|
||||
resetForm() {
|
||||
this.form.teamName = '';
|
||||
this.form.activityType = '';
|
||||
this.teamLogo = null;
|
||||
this.uploadedLogoFile = null;
|
||||
if (this.$refs.fileUpload) {
|
||||
this.$refs.fileUpload.value = '';
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.tab-content {
|
||||
display: flex;
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
<template>
|
||||
<div class="card license-card">
|
||||
<span>لایسنسهای قابل استفاده: {{ remainingCapacity }}</span>
|
||||
<div class="buy-subscription" @click="goToBuySubscription">
|
||||
<p>خرید اشتراک</p>
|
||||
<span>
|
||||
<div>
|
||||
<!-- License Info -->
|
||||
<div class="card license-card">
|
||||
<span>لایسنسهای قابل استفاده: {{ remainingCapacity }}</span>
|
||||
<div
|
||||
v-if="!hasActiveSubscription"
|
||||
class="buy-subscription"
|
||||
@click="goToBuySubscription"
|
||||
>
|
||||
<p>خرید اشتراک</p>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
|
@ -26,30 +31,29 @@
|
|||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-cards">
|
||||
<div class="user-card" v-for="(user, index) in userList" :key="index">
|
||||
<div class="user-card-header">
|
||||
<img :src="user.avatar" class="user-avatar" alt="avatar" />
|
||||
<div class="user-info-box">
|
||||
<div class="user-info-tags">
|
||||
<div class="user-name">{{ user.name }}</div>
|
||||
<div class="user-email">{{ user.email }}</div>
|
||||
</div>
|
||||
<div class="user-activity">
|
||||
<div class="user-role">{{ user.role }}</div>
|
||||
<div class="user-version">{{ user.version }}</div>
|
||||
<!-- User List -->
|
||||
<div class="user-cards">
|
||||
<div v-for="(user, index) in userList" :key="index" class="user-card">
|
||||
<div class="user-card-header">
|
||||
<img :src="user.avatar" class="user-avatar" alt="avatar" />
|
||||
<div class="user-info-box">
|
||||
<div class="user-info-tags">
|
||||
<div class="user-name">{{ user.name }}</div>
|
||||
<div class="user-email">{{ user.email }}</div>
|
||||
</div>
|
||||
<div class="user-activity">
|
||||
<div class="user-role">{{ user.role }}</div>
|
||||
<div class="user-version">{{ user.version }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-footer">
|
||||
<span>اکانت XRoom</span>
|
||||
<div class="user-actions">
|
||||
<button>
|
||||
<i class="icon">
|
||||
<div class="user-footer">
|
||||
<span>اکانت XRoom</span>
|
||||
<div class="user-actions">
|
||||
<button>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="25"
|
||||
|
@ -86,10 +90,8 @@
|
|||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</i>
|
||||
</button>
|
||||
<button>
|
||||
<i class="icon">
|
||||
</button>
|
||||
<button>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="25"
|
||||
|
@ -133,59 +135,55 @@
|
|||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</i>
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Add User Card -->
|
||||
<div class="user-card add-card" @click="openAddUserModal">
|
||||
<span class="add-text">
|
||||
<span style="font-size: 23px; margin-left: 0.5rem;">+</span>
|
||||
اضافه کردن کاربر جدید
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-card add-card" @click="openAddUserModal">
|
||||
<span class="add-text">
|
||||
<span style="font-size: 23px; margin-left: 0.5rem;">+</span> اضافه کردن کاربر جدید
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<AddUserModal
|
||||
:isVisible="isAddUserModalVisible"
|
||||
@close="closeAddUserModal"
|
||||
@add-user="submitNewUser"
|
||||
/>
|
||||
<!-- Add User Modal -->
|
||||
<AddUserModal
|
||||
:is-visible="isAddUserModalVisible"
|
||||
@close="closeAddUserModal"
|
||||
@add-user="submitNewUser"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddUserModal from '@/components/AddUserModal.vue';
|
||||
|
||||
export default {
|
||||
name: 'UsersTab',
|
||||
components: {
|
||||
AddUserModal,
|
||||
},
|
||||
name: 'TeamUser',
|
||||
components: { AddUserModal },
|
||||
props: {
|
||||
userList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
teamMemberCapacity: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
subscriptionCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
userList: { type: Array, default: () => [] },
|
||||
teamMemberCapacity: { type: Number, default: 0 },
|
||||
subscriptionCount: { type: Number, default: 0 },
|
||||
hasActiveSubscription: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isAddUserModalVisible: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
remainingCapacity() {
|
||||
const capacity = this.subscriptionCount - this.teamMemberCapacity;
|
||||
return capacity;
|
||||
return this.subscriptionCount - this.teamMemberCapacity;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openAddUserModal() {
|
||||
if (this.remainingCapacity <= 0) {
|
||||
alert('ظرفیت تیم پر شده است. لطفاً اشتراک جدیدی خریداری کنید.');
|
||||
this.goToBuySubscription();
|
||||
this.$emit('change-tab', 'buy-subscription');
|
||||
alert('اشتراک فعالی ندارید , لطفا اشتراک تهیه نمایید.');
|
||||
return;
|
||||
}
|
||||
this.isAddUserModalVisible = true;
|
||||
|
@ -201,15 +199,9 @@ export default {
|
|||
this.$emit('change-tab', 'buy-subscription');
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isAddUserModalVisible: false,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
/* User Info Section */
|
||||
.user-info {
|
||||
|
@ -283,6 +275,10 @@ export default {
|
|||
color: #3a57e8;
|
||||
font-weight: 600;
|
||||
font-size: 17px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 110px;
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
.buy-subscription {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- Description -->
|
||||
<div class="section-description">
|
||||
<div class="section-title">مدیریت جلسات</div>
|
||||
<p class="title-description">
|
||||
|
@ -8,7 +7,6 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Meeting Section -->
|
||||
<div class="meeting-section">
|
||||
<div class="meeting-filters">
|
||||
<div class="search-section">
|
||||
|
@ -65,14 +63,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Meet Discover -->
|
||||
<div :class="filteredMeetings.length === 0 ? 'meet-discover' : 'meetings-container'">
|
||||
<span class="discover-result" v-if="filteredMeetings.length === 0">
|
||||
هیچ جلسهای یافت نشد. با کلیک کردن، یک جلسه جدید ایجاد کنید
|
||||
</span>
|
||||
<div v-else class="meetings-list">
|
||||
<div v-for="meeting in filteredMeetings" :key="meeting.id" class="meeting-item">
|
||||
<img :src="meeting.image" alt="Meeting Image" class="meeting-image" width="120px" height="120px" />
|
||||
<img :src="meeting.image" alt="Meeting Image" class="meeting-image" width="120" height="120" />
|
||||
<div class="meeting-details" style="margin-right: 10px;">
|
||||
<h3 class="meet-title">{{ meeting.title }}</h3>
|
||||
<p class="meet-capacity">
|
||||
|
@ -120,7 +117,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Meeting Modal -->
|
||||
<CreateMeetingModal
|
||||
:is-open="showModal"
|
||||
@create-meeting="createNewMeeting"
|
||||
|
@ -133,11 +129,11 @@
|
|||
import CreateMeetingModal from '@/components/CreateMeetingModal.vue';
|
||||
import axios from 'axios';
|
||||
|
||||
const API_BASE_URL = 'http://my.xroomapp.com:8000';
|
||||
|
||||
export default {
|
||||
name: 'DashboardPage',
|
||||
components: {
|
||||
CreateMeetingModal,
|
||||
},
|
||||
name: 'Meetings',
|
||||
components: { CreateMeetingModal },
|
||||
data() {
|
||||
return {
|
||||
searchQuery: '',
|
||||
|
@ -179,15 +175,14 @@ export default {
|
|||
filterMeetings() {
|
||||
let filtered = this.meetings;
|
||||
if (this.searchQuery) {
|
||||
filtered = filtered.filter(
|
||||
(meeting) =>
|
||||
meeting.title.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
|
||||
meeting.type.toLowerCase().includes(this.searchQuery.toLowerCase())
|
||||
filtered = filtered.filter((meeting) =>
|
||||
[meeting.title, meeting.type].some((field) =>
|
||||
field.toLowerCase().includes(this.searchQuery.toLowerCase())
|
||||
)
|
||||
);
|
||||
}
|
||||
if (this.activeFilter === 'future') {
|
||||
const now = new Date();
|
||||
filtered = filtered.filter((meeting) => new Date(meeting.date) > now);
|
||||
filtered = filtered.filter((meeting) => new Date(meeting.date) > new Date());
|
||||
}
|
||||
this.filteredMeetings = filtered;
|
||||
},
|
||||
|
@ -197,59 +192,40 @@ export default {
|
|||
},
|
||||
async refreshToken() {
|
||||
try {
|
||||
const response = await axios.post('http://my.xroomapp.com:8000/refresh_token', {
|
||||
const response = await axios.post(`${API_BASE_URL}/refresh_token`, {
|
||||
refresh_token: localStorage.getItem('refresh_token'),
|
||||
});
|
||||
const newToken = response.data.access_token;
|
||||
localStorage.setItem('token', newToken);
|
||||
return newToken;
|
||||
} catch (error) {
|
||||
console.error('خطا در refresh توکن:', error);
|
||||
alert('لطفاً دوباره وارد شوید');
|
||||
window.location.href = '/login';
|
||||
return null;
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
async createNewMeeting(meetingData) {
|
||||
try {
|
||||
console.log('دادههای ارسالی به API:', JSON.stringify(meetingData, null, 2));
|
||||
let token = localStorage.getItem('token');
|
||||
console.log('توکن اولیه:', token);
|
||||
if (!token) {
|
||||
throw new Error('توکن احراز هویت پیدا نشد');
|
||||
}
|
||||
if (!token) throw new Error('توکن احراز هویت پیدا نشد');
|
||||
|
||||
let response = await axios.post(
|
||||
'http://my.xroomapp.com:8000/add_meeting',
|
||||
meetingData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Token ${token.trim()}`,
|
||||
},
|
||||
}
|
||||
).catch(async (error) => {
|
||||
if (error.response && error.response.status === 403) {
|
||||
console.log('تلاش برای refresh توکن...');
|
||||
const config = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Token ${token.trim()}`,
|
||||
},
|
||||
};
|
||||
|
||||
let response = await axios.post(`${API_BASE_URL}/add_meeting`, meetingData, config).catch(async (error) => {
|
||||
if (error.response?.status === 403) {
|
||||
token = await this.refreshToken();
|
||||
if (token) {
|
||||
return await axios.post(
|
||||
'http://my.xroomapp.com:8000/add_meeting',
|
||||
meetingData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Token ${token.trim()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
return axios.post(`${API_BASE_URL}/add_meeting`, meetingData, {
|
||||
headers: { ...config.headers, Authorization: `Token ${token.trim()}` },
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
console.log('پاسخ API:', response.data);
|
||||
|
||||
const newMeeting = {
|
||||
id: response.data.meeting.id,
|
||||
title: response.data.meeting.name,
|
||||
|
@ -264,37 +240,14 @@ export default {
|
|||
this.showModal = false;
|
||||
alert('جلسه با موفقیت ایجاد شد!');
|
||||
} catch (error) {
|
||||
console.error('خطا در ارسال درخواست به API:', error);
|
||||
if (error.response) {
|
||||
console.error('جزئیات پاسخ سرور:', error.response.data);
|
||||
alert(`خطایی در ایجاد جلسه رخ داد: ${error.response.data.message || error.message}`);
|
||||
} else {
|
||||
alert(`خطایی در ایجاد جلسه رخ داد: ${error.message}`);
|
||||
}
|
||||
alert(`خطایی در ایجاد جلسه رخ داد: ${error.response?.data?.message || error.message}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style scoped>
|
||||
/* .dashboard-page {
|
||||
margin-right: 360px;
|
||||
padding: 20px;
|
||||
direction: rtl;
|
||||
font-family: IRANSansXFaNum, sans-serif;
|
||||
}
|
||||
.content {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 20px;
|
||||
padding: 35px 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
}
|
||||
*/
|
||||
|
||||
.section-title {
|
||||
font-size: 20px;
|
||||
|
|
|
@ -1,72 +1,66 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- Description -->
|
||||
<div class="section-description">
|
||||
<div class="section-title">مدیریت اعضا</div>
|
||||
<p>
|
||||
در این بخش به شما امکان میدهد تا اتاقها، فایلها و جلسات را با همکاران خود به اشتراک بگذارید. در این بخش میتوانید تیم خود را مدیریت کنید.
|
||||
</p>
|
||||
</div>
|
||||
<!-- Tab Buttons -->
|
||||
<div class="tab-buttons">
|
||||
<button
|
||||
:class="['tab-btn', activeTab === 'users' ? 'active' : '']"
|
||||
@click="activeTab = 'users'"
|
||||
>
|
||||
کاربران
|
||||
</button>
|
||||
<button
|
||||
:class="['tab-btn', activeTab === 'buy-subscription' ? 'active' : '']"
|
||||
@click="activeTab = 'buy-subscription'"
|
||||
>
|
||||
خرید اشتراک
|
||||
</button>
|
||||
<button
|
||||
:class="['tab-btn', activeTab === 'membership' ? 'active' : '']"
|
||||
@click="activeTab = 'membership'"
|
||||
>
|
||||
اشتراک ها
|
||||
</button>
|
||||
<button
|
||||
:class="['tab-btn', activeTab === 'details' ? 'active' : '']"
|
||||
@click="activeTab = 'details'"
|
||||
>
|
||||
جزئیات
|
||||
</button>
|
||||
</div>
|
||||
<!-- Tab Content -->
|
||||
<div v-if="activeTab === 'users'">
|
||||
<TeamUser
|
||||
:userList="userList"
|
||||
:teamMemberCapacity="teamMemberCapacity"
|
||||
:subscriptionCount="subscriptionCount"
|
||||
@add-user="submitNewUser"
|
||||
@change-tab="changeTab"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="activeTab === 'membership'">
|
||||
<Membership
|
||||
:subscriptionCount="subscriptionCount"
|
||||
:teamMemberCapacity="teamMemberCapacity"
|
||||
:isBillingModalVisible="isBillingModalVisible"
|
||||
@change-tab="changeTab"
|
||||
@update:isBillingModalVisible="isBillingModalVisible = $event"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="activeTab === 'details'">
|
||||
<TeamDetails @update:teamData="handleTeamData" />
|
||||
</div>
|
||||
<div v-if="activeTab === 'buy-subscription'">
|
||||
<BuySubscription
|
||||
:memberCount="memberCount"
|
||||
:availableMemberOptions="availableMemberOptions"
|
||||
:baseUrl="baseUrl"
|
||||
@update:memberCount="memberCount = $event"
|
||||
@plan-selected="selectedPlan = $event"
|
||||
@payment-success="handlePaymentSuccess"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Section Description -->
|
||||
<div class="section-description">
|
||||
<div class="section-title">مدیریت اعضا</div>
|
||||
<p>در این بخش میتوانید اتاقها، فایلها و جلسات را با همکاران خود به اشتراک بگذارید و تیم خود را مدیریت کنید.</p>
|
||||
</div>
|
||||
|
||||
<!-- Tab Buttons -->
|
||||
<div class="tab-buttons">
|
||||
<button
|
||||
:class="['tab-btn', { active: activeTab === 'users' }]"
|
||||
@click="activeTab = 'users'"
|
||||
>کاربران</button>
|
||||
<button
|
||||
:class="['tab-btn', { active: activeTab === 'buy-subscription' }]"
|
||||
@click="activeTab = 'buy-subscription'"
|
||||
>خرید اشتراک</button>
|
||||
<button
|
||||
:class="['tab-btn', { active: activeTab === 'membership' }]"
|
||||
@click="activeTab = 'membership'"
|
||||
>اشتراکها</button>
|
||||
<button
|
||||
:class="['tab-btn', { active: activeTab === 'details' }]"
|
||||
@click="activeTab = 'details'"
|
||||
>جزئیات</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div v-if="activeTab === 'users'">
|
||||
<TeamUser
|
||||
:user-list="userList"
|
||||
:team-member-capacity="teamMemberCapacity"
|
||||
:subscription-count="subscriptionCount"
|
||||
:has-active-subscription="hasActiveSubscription"
|
||||
@add-user="submitNewUser"
|
||||
@change-tab="changeTab"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="activeTab === 'membership'">
|
||||
<Membership
|
||||
:subscription-count="subscriptionCount"
|
||||
:team-member-capacity="teamMemberCapacity"
|
||||
:is-billing-modal-visible="isBillingModalVisible"
|
||||
@change-tab="changeTab"
|
||||
@update:is-billing-modal-visible="isBillingModalVisible = $event"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="activeTab === 'details'">
|
||||
<TeamDetails @update:team-data="handleTeamData" />
|
||||
</div>
|
||||
<div v-if="activeTab === 'buy-subscription'">
|
||||
<BuySubscription
|
||||
:member-count="memberCount"
|
||||
:available-member-options="availableMemberOptions"
|
||||
:base-url="baseUrl"
|
||||
:has-active-subscription="hasActiveSubscription"
|
||||
:has-expired-subscription="hasExpiredSubscription"
|
||||
@update:member-count="memberCount = $event"
|
||||
@payment-success="handlePaymentSuccess"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -77,160 +71,127 @@ import TeamDetails from '@/components/TeamDetails.vue';
|
|||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: 'DashboardPage',
|
||||
components: {
|
||||
TeamUser,
|
||||
BuySubscription,
|
||||
Membership,
|
||||
TeamDetails,
|
||||
},
|
||||
name: 'Team',
|
||||
components: { TeamUser, BuySubscription, Membership, TeamDetails },
|
||||
data() {
|
||||
return {
|
||||
isBillingModalVisible: false,
|
||||
activeTab: 'users',
|
||||
userList: [],
|
||||
memberCount: 5,
|
||||
availableMemberOptions: [5, 10, 20, 100],
|
||||
selectedPlan: null,
|
||||
userList: [],
|
||||
activeTab: 'users',
|
||||
previewUrl: '',
|
||||
currentPreviewIndex: null,
|
||||
currentPreviewType: null,
|
||||
videoOptions: {
|
||||
autoplay: false,
|
||||
controls: true,
|
||||
sources: [
|
||||
{
|
||||
type: 'video/mp4',
|
||||
src: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
userData: {
|
||||
customer: {},
|
||||
user: {
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
},
|
||||
images: [],
|
||||
pdfs: [],
|
||||
videos: [],
|
||||
glbs: [],
|
||||
subscription: null,
|
||||
},
|
||||
newFileName: '',
|
||||
selectedFile: null,
|
||||
uploading: false,
|
||||
baseUrl: 'http://194.62.43.230:8000',
|
||||
currentUploadType: 'image',
|
||||
dialogTitle: 'آپلود فایل جدید',
|
||||
fileAccept: '*/*',
|
||||
teamMemberCapacity: 0,
|
||||
subscriptionCount: 0,
|
||||
hasActiveSubscription: false,
|
||||
hasExpiredSubscription: false, // جدید: بررسی اشتراک منقضیشده
|
||||
subscriptionEndTime: null, // جدید: ذخیره تاریخ انقضای اشتراک
|
||||
teamId: null,
|
||||
subscriptionId: null,
|
||||
isBillingModalVisible: false,
|
||||
baseUrl: 'http://my.xroomapp.com:8000',
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetchUserData();
|
||||
this.fetchTeamMemberInfo();
|
||||
this.fetchTeamData();
|
||||
const tab = this.$route.query.tab;
|
||||
if (tab) {
|
||||
this.activeTab = tab;
|
||||
}
|
||||
this.initializeData();
|
||||
},
|
||||
methods: {
|
||||
async initializeData() {
|
||||
const tab = this.$route.query.tab;
|
||||
if (tab) this.activeTab = tab;
|
||||
await Promise.all([
|
||||
this.fetchUserData(),
|
||||
this.fetchTeamMemberInfo(),
|
||||
this.fetchTeamData(),
|
||||
]);
|
||||
},
|
||||
changeTab(tabName) {
|
||||
this.activeTab = tabName;
|
||||
},
|
||||
async fetchTeamData() {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await axios.get('http://my.xroomapp.com:8000/get_team', {
|
||||
headers: {
|
||||
Authorization: `Token ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const response = await this.axiosGet('/get_team');
|
||||
const team = response.data.teams[0];
|
||||
if (team) {
|
||||
this.teamId = team.id;
|
||||
} else {
|
||||
this.teamId = null;
|
||||
}
|
||||
this.teamId = team?.id || null;
|
||||
} catch (error) {
|
||||
alert('خطا در بارگذاری اطلاعات تیم. لطفاً دوباره تلاش کنید.');
|
||||
console.error('Error fetching team data:', error);
|
||||
alert('خطا در بارگذاری اطلاعات تیم.');
|
||||
}
|
||||
},
|
||||
async handlePaymentSuccess() {
|
||||
await this.fetchTeamMemberInfo();
|
||||
await this.fetchUserData();
|
||||
this.activeTab = 'membership';
|
||||
},
|
||||
async fetchTeamMemberInfo() {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await axios.get(`${this.baseUrl}/get_all_team_members`, {
|
||||
headers: {
|
||||
Authorization: `Token ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
this.userList = response.data.members.map((member) => ({
|
||||
name: `${member.first_name} ${member.last_name}`,
|
||||
email: member.username,
|
||||
role: 'کاربر',
|
||||
const response = await this.axiosGet('/get_all_team_members');
|
||||
this.userList = response.data.members.map(member => ({
|
||||
name: `${member.user.first_name} ${member.user.last_name}`,
|
||||
email: member.user.username,
|
||||
role: member.semat || 'کاربر',
|
||||
version: 'نسخه آزمایشی',
|
||||
avatar: 'https://models.readyplayer.me/681f59760bc631a87ad25172.png',
|
||||
avatar: member.profile_img || 'https://models.readyplayer.me/681f59760bc631a87ad25172.png',
|
||||
}));
|
||||
this.teamMemberCapacity = response.data.members.length;
|
||||
} catch (error) {
|
||||
alert('خطا در بارگذاری اطلاعات اعضای تیم. لطفاً دوباره تلاش کنید.');
|
||||
console.error('Error fetching team members:', error);
|
||||
alert('خطا در بارگذاری اطلاعات اعضای تیم.');
|
||||
}
|
||||
},
|
||||
async fetchUserData() {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await axios.get(`${this.baseUrl}/getInfo`, {
|
||||
headers: {
|
||||
Authorization: `Token ${token}`,
|
||||
},
|
||||
});
|
||||
this.userData = response.data;
|
||||
if (this.userData.data.subscription) {
|
||||
this.subscriptionCount = this.userData.data.subscription || 0;
|
||||
} else {
|
||||
this.subscriptionCount = 0;
|
||||
}
|
||||
const response = await this.axiosGet('/get_user_subscriptions');
|
||||
const subscriptions = response.data.subscriptions || [];
|
||||
this.subscriptionCount = subscriptions.reduce((total, sub) => total + sub.user_count, 0);
|
||||
this.subscriptionId = subscriptions[0]?.id || null;
|
||||
this.subscriptionEndTime = subscriptions[0]?.endTime || null; // جدید: تاریخ انقضا
|
||||
const now = new Date();
|
||||
const isExpiredByTime = this.subscriptionEndTime && new Date(this.subscriptionEndTime) < now;
|
||||
const isExpiredByCapacity = this.subscriptionCount <= this.teamMemberCapacity;
|
||||
this.hasActiveSubscription = subscriptions.length > 0 && !isExpiredByTime && !isExpiredByCapacity;
|
||||
this.hasExpiredSubscription = subscriptions.length > 0 && (isExpiredByTime || isExpiredByCapacity);
|
||||
} catch (error) {
|
||||
alert('خطا در بارگذاری اطلاعات کاربر. لطفاً دوباره تلاش کنید.');
|
||||
console.error('Error fetching user data:', error);
|
||||
alert('خطا در بارگذاری اطلاعات اشتراک.');
|
||||
}
|
||||
},
|
||||
async handlePaymentSuccess({ subscriptionId }) {
|
||||
try {
|
||||
this.subscriptionId = subscriptionId;
|
||||
await Promise.all([
|
||||
this.fetchUserData(),
|
||||
this.fetchTeamMemberInfo(),
|
||||
this.fetchTeamData(),
|
||||
]);
|
||||
|
||||
if (!this.teamId && this.subscriptionId) {
|
||||
await this.createTeam();
|
||||
alert('اشتراک و تیم به درستی ساخته شد.');
|
||||
} else if (this.teamId) {
|
||||
alert('تیم از قبل وجود دارد.');
|
||||
}
|
||||
this.activeTab = 'membership';
|
||||
} catch (error) {
|
||||
console.error('Error handling payment success:', error);
|
||||
alert('خطا در پردازش پرداخت یا ساخت تیم.');
|
||||
}
|
||||
},
|
||||
async createTeam() {
|
||||
const teamData = {
|
||||
name: 'تیم 1',
|
||||
description: 'فعالیت',
|
||||
max_persons: this.subscriptionCount.toString(),
|
||||
subscriptionId: this.subscriptionId,
|
||||
};
|
||||
await this.axiosPost('/add_team', teamData);
|
||||
await this.fetchTeamData();
|
||||
},
|
||||
async submitNewUser(newUser) {
|
||||
const remainingCapacity = this.subscriptionCount - this.teamMemberCapacity;
|
||||
if (remainingCapacity <= 0) {
|
||||
alert('ظرفیت تیم پر شده است. لطفاً اشتراک جدیدی خریداری کنید.');
|
||||
if (this.subscriptionCount - this.teamMemberCapacity <= 0) {
|
||||
alert('اشتراک فعالی ندارید , اشتراک تهیه نمایید.');
|
||||
this.activeTab = 'buy-subscription';
|
||||
return;
|
||||
}
|
||||
if (!this.teamId) {
|
||||
alert('خطا: اطلاعات تیم یافت نشد. لطفاً دوباره تلاش کنید.');
|
||||
alert('خطا: اطلاعات تیم یافت نشد.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
await axios.post(
|
||||
'http://my.xroomapp.com:8000/add_teamMember/',
|
||||
{
|
||||
...newUser,
|
||||
teamId: this.teamId,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
await this.axiosPost('/add_teamMember/', { ...newUser, teamId: this.teamId });
|
||||
this.userList.push({
|
||||
...newUser,
|
||||
avatar: 'https://models.readyplayer.me/681f59760bc631a87ad25172.png',
|
||||
|
@ -239,227 +200,39 @@ export default {
|
|||
});
|
||||
this.teamMemberCapacity++;
|
||||
await this.fetchTeamMemberInfo();
|
||||
alert('کاربر با موفقیت اضافه شد');
|
||||
alert('کاربر با موفقیت اضافه شد.');
|
||||
} catch (error) {
|
||||
alert('خطا در اضافه کردن کاربر. لطفاً دوباره تلاش کنید.');
|
||||
console.error('Error adding user:', error);
|
||||
alert('خطا در اضافه کردن کاربر.');
|
||||
}
|
||||
},
|
||||
handleBackdropClick(event) {
|
||||
if (event.target === this.$refs.filePreviewDialog) {
|
||||
this.closePreviewDialog();
|
||||
}
|
||||
handleTeamData(data) {
|
||||
console.log('Team data updated:', data);
|
||||
},
|
||||
openPreviewDialog(type, index, url) {
|
||||
if (type === 'video') {
|
||||
this.videoOptions.sources[0].src = url;
|
||||
this.$nextTick(() => {
|
||||
this.$refs.filePreviewDialog?.showModal();
|
||||
});
|
||||
}
|
||||
if (!this.$refs.filePreviewDialog) {
|
||||
return;
|
||||
}
|
||||
this.currentPreviewType = type;
|
||||
this.currentPreviewIndex = index;
|
||||
this.previewUrl = url;
|
||||
if (type === 'video') {
|
||||
this.videoOptions.sources[0].src = url;
|
||||
this.videoOptions.poster = this.getVideoThumbnail();
|
||||
this.previewUrl = url;
|
||||
} else {
|
||||
this.previewUrl = url;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs.filePreviewDialog?.showModal();
|
||||
async axiosGet(endpoint) {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) throw new Error('توکن احراز هویت یافت نشد.');
|
||||
return await axios.get(`${this.baseUrl}${endpoint}`, {
|
||||
headers: { Authorization: `Token ${token}`, 'Content-Type': 'application/json' },
|
||||
});
|
||||
if (type === 'image') {
|
||||
this.previewImageUrl = url;
|
||||
this.previewPdfUrl = '';
|
||||
} else if (type === 'pdf') {
|
||||
this.previewPdfUrl = url;
|
||||
this.previewImageUrl = '';
|
||||
}
|
||||
this.$refs.filePreviewDialog.showModal();
|
||||
},
|
||||
getVideoThumbnail() {
|
||||
return 'https://cdn-icons-png.flaticon.com/512/2839/2839038.png';
|
||||
},
|
||||
closePreviewDialog() {
|
||||
const dialog = this.$refs.filePreviewDialog;
|
||||
if (dialog && typeof dialog.close === 'function') {
|
||||
dialog.close();
|
||||
}
|
||||
this.previewUrl = '';
|
||||
this.currentPreviewIndex = null;
|
||||
this.currentPreviewType = null;
|
||||
},
|
||||
async downloadFile() {
|
||||
const url =
|
||||
this.currentPreviewType === 'image' ? this.previewImageUrl : this.previewPdfUrl;
|
||||
if (!url) return;
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
const downloadUrl = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
if (this.currentPreviewType === 'image') {
|
||||
a.download = `image-${new Date().getTime()}.${url.split('.').pop()}`;
|
||||
} else if (this.currentPreviewType === 'pdf') {
|
||||
a.download = `document-${new Date().getTime()}.pdf`;
|
||||
}
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(downloadUrl);
|
||||
document.body.removeChild(a);
|
||||
} catch (error) {
|
||||
alert('خطا در دانلود فایل');
|
||||
}
|
||||
},
|
||||
async deleteFile() {
|
||||
if (this.currentPreviewIndex === null || !this.currentPreviewType) return;
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
let deleteUrl = '';
|
||||
let itemId = '';
|
||||
let fileArray = [];
|
||||
switch (this.currentPreviewType) {
|
||||
case 'image':
|
||||
fileArray = this.userData.images;
|
||||
itemId = fileArray[this.currentPreviewIndex].id;
|
||||
deleteUrl = `${this.baseUrl}/deleteImage/${itemId}/`;
|
||||
break;
|
||||
case 'pdf':
|
||||
fileArray = this.userData.pdfs;
|
||||
itemId = fileArray[this.currentPreviewIndex].id;
|
||||
deleteUrl = `${this.baseUrl}/deletePdf/${itemId}/`;
|
||||
break;
|
||||
case 'video':
|
||||
fileArray = this.userData.videos;
|
||||
itemId = fileArray[this.currentPreviewIndex].id;
|
||||
deleteUrl = `${this.baseUrl}/deleteVideo/${itemId}/`;
|
||||
break;
|
||||
case 'glb':
|
||||
fileArray = this.userData.glbs;
|
||||
itemId = fileArray[this.currentPreviewIndex].id;
|
||||
deleteUrl = `${this.baseUrl}/deleteGlb/${itemId}/`;
|
||||
break;
|
||||
}
|
||||
await axios.delete(deleteUrl, {
|
||||
headers: {
|
||||
Authorization: `Token ${token}`,
|
||||
},
|
||||
});
|
||||
this.closePreviewDialog();
|
||||
await this.fetchUserData();
|
||||
alert('فایل با موفقیت حذف شد');
|
||||
} catch (error) {
|
||||
alert('خطا در حذف فایل');
|
||||
}
|
||||
},
|
||||
getFullImageUrl(relativePath) {
|
||||
if (!relativePath) return '';
|
||||
return `${this.baseUrl}${relativePath}`;
|
||||
},
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('fa-IR');
|
||||
},
|
||||
openDialog(type) {
|
||||
this.currentUploadType = type;
|
||||
switch (type) {
|
||||
case 'image':
|
||||
this.dialogTitle = 'آپلود تصویر جدید';
|
||||
this.fileAccept = 'image/*';
|
||||
break;
|
||||
case 'pdf':
|
||||
this.dialogTitle = 'آپلود فایل PDF';
|
||||
this.fileAccept = '.pdf';
|
||||
break;
|
||||
case 'video':
|
||||
this.dialogTitle = 'آپلود ویدیو';
|
||||
this.fileAccept = 'video/*';
|
||||
break;
|
||||
case 'glb':
|
||||
this.dialogTitle = 'آپلود مدل 3D';
|
||||
this.fileAccept = '.glb';
|
||||
break;
|
||||
}
|
||||
this.$refs.newFileDialog.showModal();
|
||||
},
|
||||
closeDialog() {
|
||||
this.newFileName = '';
|
||||
this.selectedFile = null;
|
||||
this.$refs.newFileDialog.close();
|
||||
},
|
||||
handleFileChange(event) {
|
||||
this.selectedFile = event.target.files[0];
|
||||
},
|
||||
async uploadFile() {
|
||||
if (!this.selectedFile) {
|
||||
return;
|
||||
}
|
||||
this.uploading = true;
|
||||
const formData = new FormData();
|
||||
formData.append('name', this.newFileName || this.selectedFile.name);
|
||||
switch (this.currentUploadType) {
|
||||
case 'image':
|
||||
formData.append('image', this.selectedFile);
|
||||
break;
|
||||
case 'pdf':
|
||||
formData.append('pdf', this.selectedFile);
|
||||
break;
|
||||
case 'video':
|
||||
formData.append('video', this.selectedFile);
|
||||
break;
|
||||
case 'glb':
|
||||
formData.append('glb', this.selectedFile);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
let uploadUrl = '';
|
||||
switch (this.currentUploadType) {
|
||||
case 'image':
|
||||
uploadUrl = `${this.baseUrl}/uploadImage/`;
|
||||
break;
|
||||
case 'pdf':
|
||||
uploadUrl = `${this.baseUrl}/uploadPdf/`;
|
||||
break;
|
||||
case 'video':
|
||||
uploadUrl = `${this.baseUrl}/uploadVideo/`;
|
||||
break;
|
||||
case 'glb':
|
||||
uploadUrl = `${this.baseUrl}/uploadGlb/`;
|
||||
break;
|
||||
}
|
||||
await axios.post(uploadUrl, formData, {
|
||||
headers: {
|
||||
Authorization: `Token ${token}`,
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
this.closeDialog();
|
||||
await this.fetchUserData();
|
||||
alert('فایل با موفقیت آپلود شد');
|
||||
} catch (error) {
|
||||
alert('خطا در آپلود فایل');
|
||||
} finally {
|
||||
this.uploading = false;
|
||||
}
|
||||
async axiosPost(endpoint, data) {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) throw new Error('توکن احراز هویت یافت نشد.');
|
||||
return await axios.post(`${this.baseUrl}${endpoint}`, data, {
|
||||
headers: { Authorization: `Token ${token}`, 'Content-Type': 'application/json' },
|
||||
});
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$route.query.tab'(newTab) {
|
||||
if (newTab) {
|
||||
this.activeTab = newTab;
|
||||
}
|
||||
if (newTab) this.activeTab = newTab;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
|
||||
.section-title {
|
||||
|
|
Loading…
Reference in New Issue
Block a user