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