NASER add task functions
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import * as Lexxy from "@37signals/lexxy"
|
||||
|
||||
// Rich Text Laravel uses a custom attachment tag name, so we must configure it here...
|
||||
Lexxy.configure({ global: { attachmentTagName: "rich-text-attachment" } })
|
||||
|
||||
export default Lexxy;
|
||||
@@ -0,0 +1,5 @@
|
||||
import Trix from 'trix';
|
||||
|
||||
window.Trix = Trix;
|
||||
|
||||
export default Trix;
|
||||
@@ -464,19 +464,23 @@
|
||||
closeModal()
|
||||
}
|
||||
})
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
function autoDismissFlashMessage() {
|
||||
const errorBox = document.getElementById('error-box')
|
||||
if (errorBox) {
|
||||
// Fade out the error box after 3 seconds
|
||||
setTimeout(() => {
|
||||
errorBox.classList.add('opacity-0') // Add fade-out class
|
||||
// Remove the error box after the fade-out transition ends
|
||||
errorBox.addEventListener('transitionend', () => {
|
||||
errorBox.remove()
|
||||
})
|
||||
}, 3000) // Duration before starting fade out
|
||||
}
|
||||
})
|
||||
if (!errorBox) return
|
||||
|
||||
setTimeout(() => {
|
||||
errorBox.classList.add('opacity-0')
|
||||
errorBox.addEventListener('transitionend', () => {
|
||||
errorBox.remove()
|
||||
})
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', autoDismissFlashMessage)
|
||||
} else {
|
||||
autoDismissFlashMessage()
|
||||
}
|
||||
</script>
|
||||
@yield('script')
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ if (!empty($user->tasks)) {
|
||||
});
|
||||
}
|
||||
//dd(count($break = $user->attendanceBreaks));
|
||||
if (count($user->attendanceBreaks) > 0){
|
||||
if (count($user->attendanceBreaks) > 0) {
|
||||
$break = $user->attendanceBreaks[0];
|
||||
if ($break->break_end == null) {
|
||||
$now = Carbon::now()->format('Y-m-d H:i:s');
|
||||
@@ -68,7 +68,7 @@ if (count($user->attendanceBreaks) > 0){
|
||||
{{-- </button>--}}
|
||||
@if (!isset($_COOKIE['token-0']))
|
||||
|
||||
<form id="attendanceForm" class="form items-center" action="{{ url('attendance/enter') }}" method="post"
|
||||
<form id="attendanceForm" class="form items-center" {{--action="{{ url('attendance/enter') }}" method="post"--}}
|
||||
style="text-align: left">
|
||||
@csrf
|
||||
<p class="title text-center font-bold text-lg mb-2">سامانه گذارش کار</p>
|
||||
@@ -89,6 +89,12 @@ if (count($user->attendanceBreaks) > 0){
|
||||
style="width: 100%">
|
||||
ورود/خروج
|
||||
</button>
|
||||
sghl<button id="submitBtn" disabled type="button"
|
||||
class="bg-gray-400 text-white rounded cursor-not-allowed rounded-xl py-2 px-4 max-w-60 self-center"
|
||||
style="width: 100%">
|
||||
ورود/خروج
|
||||
</button>
|
||||
<div id="output" class="text-red-500 text-sm mt-2 text-center"></div>
|
||||
|
||||
<div class="flex justify-flex max-w-[240px]" style="align-items: center;">
|
||||
<button type="button"
|
||||
@@ -111,13 +117,13 @@ if (count($user->attendanceBreaks) > 0){
|
||||
</button>
|
||||
{{--<p>{{Carbon::now()}}</p>--}}
|
||||
</form>
|
||||
{{-- @if(session('open_task_modal'))--}}
|
||||
{{-- <script>--}}
|
||||
{{-- window.onload = () => {--}}
|
||||
{{-- openTaskModal();--}}
|
||||
{{-- };--}}
|
||||
{{-- </script>--}}
|
||||
{{-- @endif--}}
|
||||
{{-- @if(session('open_task_modal'))--}}
|
||||
{{-- <script>--}}
|
||||
{{-- window.onload = () => {--}}
|
||||
{{-- openTaskModal();--}}
|
||||
{{-- };--}}
|
||||
{{-- </script>--}}
|
||||
{{-- @endif--}}
|
||||
|
||||
@else
|
||||
|
||||
@@ -253,19 +259,44 @@ if (count($user->attendanceBreaks) > 0){
|
||||
attestation: "direct"
|
||||
};
|
||||
submitBtn.addEventListener('click', async () => {
|
||||
|
||||
// ابتدا اعتبارسنجی اثر انگشت توسط WebAuthn
|
||||
if (!window.PublicKeyCredential) {
|
||||
alert('مرورگر شما از احراز هویت بیومتریک پشتیبانی نمیکند.');
|
||||
return;
|
||||
}
|
||||
|
||||
// یک چالش تصادفی بسازید
|
||||
const challenge = new Uint8Array(32);
|
||||
window.crypto.getRandomValues(challenge);
|
||||
|
||||
// گزینههای احراز هویت (Authentication)
|
||||
const getOptions = {
|
||||
publicKey: {
|
||||
challenge: challenge,
|
||||
timeout: 60000,
|
||||
rpId: window.location.hostname, // اختیاری؛ برای لوکالهاست خالی بگذارید
|
||||
userVerification: "required",
|
||||
allowCredentials: [] // خالی بگذارید تا هر کلید موجود نمایش داده شود
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const credential = await navigator.credentials.create({
|
||||
publicKey: createOptions
|
||||
});
|
||||
// به جای create از get استفاده کنید
|
||||
const assertion = await navigator.credentials.get(getOptions);
|
||||
console.log('احراز هویت موفق:', assertion);
|
||||
// اینجا فرم را سابمیت کنید یا هر عملیات دیگر
|
||||
attendanceForm.submit();
|
||||
} catch (error) {
|
||||
output.textContent = error.name === 'NotAllowedError' ? 'احراز هویت لغو شد یا دسترسی مجاز نبود.' : 'عملیات ناموفق بود. لطفاً دوباره امتحان کنید.';
|
||||
const message = error.name === 'NotAllowedError'
|
||||
? 'احراز هویت لغو شد یا دسترسی مجاز نبود.'
|
||||
: 'عملیات ناموفق بود. لطفاً دوباره امتحان کنید.';
|
||||
const output = document.getElementById('output');
|
||||
if (output) {
|
||||
output.textContent = message;
|
||||
} else {
|
||||
alert(message);
|
||||
}
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -415,7 +446,6 @@ if (count($user->attendanceBreaks) > 0){
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@endif
|
||||
|
||||
</div>
|
||||
@@ -447,7 +477,7 @@ if (count($user->attendanceBreaks) > 0){
|
||||
class=" example1 form-control border p-2 rounded date-picker-input" value=""
|
||||
placeholder="تاریخ"
|
||||
readonly
|
||||
>
|
||||
>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -885,329 +915,330 @@ if (count($user->attendanceBreaks) > 0){
|
||||
method: "POST",
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
if (res.ok) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
window.location.reload();
|
||||
// breakBtn.textContent = 'خطا';
|
||||
// breakBtn.disabled = false;
|
||||
// console.error('Server error on break action:', res.statusText);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
window.location.reload();
|
||||
|
||||
// breakBtn.textContent = 'خطای شبکه';
|
||||
// breakBtn.disabled = false;
|
||||
// console.error('Network error:', err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{{--let breakStarted = false;--}}
|
||||
{{--let breakStartTime = null;--}}
|
||||
{{--let breakTimer = null;--}}
|
||||
|
||||
{{--const breakBtn = document.getElementById('breakAttendance');--}}
|
||||
|
||||
{{--function formatLocalDatetime(date) {--}}
|
||||
{{-- const year = date.getFullYear();--}}
|
||||
{{-- const month = String(date.getMonth() + 1).padStart(2, '0');--}}
|
||||
{{-- const day = String(date.getDate()).padStart(2, '0');--}}
|
||||
{{-- const hours = String(date.getHours()).padStart(2, '0');--}}
|
||||
{{-- const minutes = String(date.getMinutes()).padStart(2, '0');--}}
|
||||
{{-- const seconds = String(date.getSeconds()).padStart(2, '0');--}}
|
||||
|
||||
{{-- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;--}}
|
||||
{{--}--}}
|
||||
|
||||
{{--function formatDuration(totalSeconds) {--}}
|
||||
{{-- const hours = Math.floor(totalSeconds / 3600);--}}
|
||||
{{-- const minutes = Math.floor((totalSeconds % 3600) / 60);--}}
|
||||
|
||||
{{-- const h = String(hours);--}}
|
||||
{{-- const i = String(minutes).padStart(2, '0');--}}
|
||||
|
||||
{{-- return `${h}:${i}`;--}}
|
||||
{{--}--}}
|
||||
|
||||
|
||||
{{--breakBtn.addEventListener('click', () => {--}}
|
||||
{{-- if (!breakStarted) {--}}
|
||||
{{-- // شروع استراحت 🚀--}}
|
||||
{{-- breakStarted = true;--}}
|
||||
{{-- breakStartTime = new Date();--}}
|
||||
|
||||
{{-- // تغییر ظاهر دکمه و شروع تایمر (همچنان M:S نمایش داده می شود)--}}
|
||||
{{-- breakBtn.classList.remove('bg-gray-500', 'hover:bg-gray-600');--}}
|
||||
{{-- breakBtn.classList.add('bg-red-500', 'hover:bg-red-600');--}}
|
||||
{{-- breakBtn.textContent = '00:00';--}}
|
||||
|
||||
{{-- let seconds = 0;--}}
|
||||
{{-- breakTimer = setInterval(() => {--}}
|
||||
{{-- seconds++;--}}
|
||||
{{-- // اینجا همچنان به صورت دقیقه:ثانیه نمایش داده می شود--}}
|
||||
{{-- const m = String(Math.floor(seconds / 60)).padStart(2, '0');--}}
|
||||
{{-- const s = String(seconds % 60).padStart(2, '0');--}}
|
||||
{{-- breakBtn.textContent = ` ${m}:${s}`;--}}
|
||||
{{-- }, 1000);--}}
|
||||
{{-- //send start time--}}
|
||||
{{-- let formData = new FormData();--}}
|
||||
{{-- formData.append('start_time', formatLocalDatetime(breakStartTime));--}}
|
||||
|
||||
{{-- formData.append('_token', '{{ csrf_token() }}');--}}
|
||||
|
||||
{{-- fetch("{{ url('attendance/break') }}", {--}}
|
||||
{{-- method: "POST",--}}
|
||||
{{-- body: formData--}}
|
||||
{{-- })--}}
|
||||
{{-- .then(res => res.json())--}}
|
||||
{{-- .then(data => console.log('✅ Break Started Logged:', data))--}}
|
||||
{{-- .catch(err => console.error('💥 Error starting break:', err));--}}
|
||||
|
||||
{{-- } else {--}}
|
||||
{{-- // پایان استراحت 🛑--}}
|
||||
{{-- breakStarted = false;--}}
|
||||
{{-- const breakEndTime = new Date();--}}
|
||||
{{-- clearInterval(breakTimer);--}}
|
||||
|
||||
{{-- // 1. محاسبه مدت زمان استراحت (بر حسب ثانیه)--}}
|
||||
{{-- const durationSec = Math.floor((breakEndTime - breakStartTime) / 1000);--}}
|
||||
|
||||
{{-- // 2. تبدیل زمان های شروع و پایان به فرمت رشته ای--}}
|
||||
{{-- const breakStartTime_str = formatLocalDatetime(breakStartTime);--}}
|
||||
{{-- const breakEndTime_str = formatLocalDatetime(breakEndTime);--}}
|
||||
|
||||
{{-- // 3. فرمت کردن مدت زمان به H:i (فقط برای نمایش در کنسول/هشدار)--}}
|
||||
{{-- const duration_formatted_h_i = formatDuration(durationSec);--}}
|
||||
|
||||
{{-- // 4. نمایش دکمه به حالت اولیه--}}
|
||||
{{-- breakBtn.classList.remove('bg-red-500', 'hover:bg-red-600');--}}
|
||||
{{-- // breakBtn.classList.add('bg-gray-500', 'hover:bg-gray-600');--}}
|
||||
{{-- breakBtn.textContent = 'استراحت';--}}
|
||||
|
||||
{{-- // 5. ارسال دادهها به سرور--}}
|
||||
{{-- let formData = new FormData();--}}
|
||||
{{-- formData.append('start_time', breakStartTime_str);--}}
|
||||
{{-- formData.append('end_time', breakEndTime_str);--}}
|
||||
{{-- formData.append('duration_seconds', durationSec);--}}
|
||||
{{-- formData.append('_token', '{{ csrf_token() }}');--}}
|
||||
|
||||
{{-- fetch("{{ url('attendance/break') }}", {--}}
|
||||
{{-- method: "POST",--}}
|
||||
{{-- body: formData--}}
|
||||
{{-- })--}}
|
||||
{{-- .then(res => res.json())--}}
|
||||
{{-- .then(data => console.log('✅ Break saved:', data))--}}
|
||||
{{-- .catch(err => console.error('💥 Error:', err));--}}
|
||||
{{-- }--}}
|
||||
{{--});--}}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{{-- ATTENDANCE MODAL--}}
|
||||
function clearTime(fieldId) {
|
||||
document.getElementById(fieldId).value = '';
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('modal-container').classList.add('hidden');
|
||||
}
|
||||
|
||||
document.getElementById('modal-container').addEventListener('click', (e) => {
|
||||
if (e.target === document.getElementById('modal-container')) {
|
||||
closeModal();
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
function confirmSubmit() {
|
||||
return confirm("آیا تغییرات اعمال شوند؟");
|
||||
}
|
||||
|
||||
function openModal(title, id, enteredAt, exitedAt) {
|
||||
const isNewEntry = id === 'new';
|
||||
const modalTitleElement = document.getElementById('modal-title');
|
||||
|
||||
// ✅ بهترین روش: انتخاب مستقیم فیلد مخفی با ID آن
|
||||
const hiddenIdInput = document.getElementById('edit_id');
|
||||
// const hiddenIdInput = document.getElementById('delete_id');
|
||||
|
||||
// انتخاب فیلد ID برای فرم حذف
|
||||
const deleteIdInput = document.getElementById('delete_id');
|
||||
const submitButton = document.querySelector('#modal-content button[type="submit"]');
|
||||
const hiddenDateInput = document.querySelector('input[name="date"]');
|
||||
|
||||
// مقداردهی به فرم حذف
|
||||
if (deleteIdInput) {
|
||||
deleteIdInput.value = id;
|
||||
}
|
||||
|
||||
const newDateLabel = document.getElementById('new-date-label');
|
||||
const newDateGroup = document.getElementById('new-date-group');
|
||||
const displayDateInput = document.getElementById('display_date');
|
||||
|
||||
const panelLink = document.getElementById('panel-access-link');
|
||||
panelLink.href = `/go_down/${id}`;
|
||||
|
||||
document.getElementById('modal-container').classList.remove('hidden');
|
||||
|
||||
const startTimeInput = document.querySelector('input[name="starttime"]');
|
||||
const endTimeInput = document.querySelector('input[name="endtime"]');
|
||||
|
||||
// تنظیم ساعتها
|
||||
if (enteredAt && !isNaN(new Date(enteredAt).getTime())) {
|
||||
startTimeInput.value = new Date(enteredAt).toLocaleTimeString('en-GB', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
} else {
|
||||
startTimeInput.value = '09:00';
|
||||
}
|
||||
|
||||
if (exitedAt && !isNaN(new Date(exitedAt).getTime())) {
|
||||
endTimeInput.value = new Date(exitedAt).toLocaleTimeString('en-GB', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
// else {
|
||||
// endTimeInput.value = '17:30';
|
||||
// }
|
||||
|
||||
|
||||
if (isNewEntry) {
|
||||
// حالت ثبت جدید
|
||||
modalTitleElement.innerText = 'ثبت روز کاری جدید';
|
||||
newDateLabel.style.display = 'block';
|
||||
newDateGroup.style.display = 'block';
|
||||
|
||||
displayDateInput.value = '';
|
||||
hiddenDateInput.value = '';
|
||||
|
||||
// ✅ اصلاح: مقداردهی 'new' به فیلد ID
|
||||
if (hiddenIdInput) hiddenIdInput.value = 'new';
|
||||
|
||||
submitButton.onclick = function () {
|
||||
if (hiddenDateInput.value === '') {
|
||||
alert("لطفاً یک تاریخ را از تقویم انتخاب کنید.");
|
||||
return false;
|
||||
}
|
||||
return confirmSubmit();
|
||||
};
|
||||
|
||||
} else {
|
||||
// حالت ویرایش
|
||||
modalTitleElement.innerText = 'تغییر ساعت کاری';
|
||||
newDateLabel.style.display = 'none';
|
||||
newDateGroup.style.display = 'none';
|
||||
|
||||
// ✅ اصلاح: استفاده از متغیر 'id' که از جدول فرستاده شده است
|
||||
if (hiddenIdInput) hiddenIdInput.value = id;
|
||||
if (hiddenDateInput) hiddenDateInput.value = enteredAt;
|
||||
|
||||
submitButton.onclick = function () {
|
||||
return confirmSubmit();
|
||||
};
|
||||
}
|
||||
|
||||
//date and clock picker
|
||||
$('.clockpicker').clockpicker({
|
||||
autoclose: false,
|
||||
placement: 'center',
|
||||
donetext: 'ثبت',
|
||||
vibrate: true,
|
||||
});
|
||||
|
||||
// --- فعالسازی تقویم ---
|
||||
$(".example1").persianDatepicker({
|
||||
format: "YYYY/MM/DD",
|
||||
altField: '#date',
|
||||
altFormat: 'YYYY-MM-DD',
|
||||
calendar: {
|
||||
persian: {
|
||||
locale: 'fa',
|
||||
leapYearMode: "astronomical"
|
||||
}
|
||||
},
|
||||
observer: true,
|
||||
initialValue: false,
|
||||
initialValueType: 'gregorian',
|
||||
autoClose: true,
|
||||
toolbox: {
|
||||
calendarSwitch: {enabled: false},
|
||||
todayButton: {enabled: true, text: {fa: "امروز"}}
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
// 1. فعالسازی Clockpicker
|
||||
$('.clockpicker').clockpicker({
|
||||
autoclose: false,
|
||||
placement: 'center',
|
||||
donetext: 'ثبت',
|
||||
vibrate: true,
|
||||
// donetext: true,
|
||||
});
|
||||
|
||||
|
||||
$(".example1").persianDatepicker({
|
||||
format: "YYYY/MM/DD",
|
||||
altField: '#date',
|
||||
altFormat: 'YYYY-MM-DD',
|
||||
calendar: {
|
||||
persian: {
|
||||
locale: 'fa',
|
||||
leapYearMode: "astronomical"
|
||||
}
|
||||
},
|
||||
observer: true,
|
||||
initialValue: false,
|
||||
initialValueType: 'gregorian',
|
||||
autoClose: true,
|
||||
toolbox: {
|
||||
calendarSwitch: {enabled: false},
|
||||
todayButton: {enabled: true, text: {fa: "امروز"}}
|
||||
},
|
||||
onSelect: function (unix) {
|
||||
// تبدیل تاریخ انتخابی به میلادی درست با timezone ایران
|
||||
const d = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
||||
console.log("Gregorian (Fixed):", d.format("YYYY-MM-DD"));
|
||||
})
|
||||
.then(res => {
|
||||
if (res.status == 'error') {
|
||||
console.log(res);
|
||||
window.location.reload();
|
||||
} else {
|
||||
console.log(res);
|
||||
breakBtn.textContent = 'خطا';
|
||||
breakBtn.disabled = false;
|
||||
console.error('Server error on break action:', res.statusText);
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
window.location.reload();
|
||||
|
||||
// breakBtn.textContent = 'خطای شبکه';
|
||||
// breakBtn.disabled = false;
|
||||
// console.error('Network error:', err);
|
||||
});
|
||||
|
||||
// 2. فعالسازی Persian Datepicker
|
||||
// $('.example1').pDatepicker({
|
||||
// format: 'YYYY/MM/DD',
|
||||
// // placement: 'center',
|
||||
// altField: '#date', // این فیلد مخفی، تاریخ میلادی تبدیل شده را دریافت میکند
|
||||
// altFormat: 'YYYY-MM-DD', // میلادی ذخیره بشه
|
||||
// // changeMonth: true,
|
||||
// // changeYear: true,
|
||||
// // showOtherMonths: true,
|
||||
// // selectOtherMonths: true,
|
||||
// autoClose: true,
|
||||
//
|
||||
// });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{{--let breakStarted = false;--}}
|
||||
{{--let breakStartTime = null;--}}
|
||||
{{--let breakTimer = null;--}}
|
||||
|
||||
{{--const breakBtn = document.getElementById('breakAttendance');--}}
|
||||
|
||||
{{--function formatLocalDatetime(date) {--}}
|
||||
{{-- const year = date.getFullYear();--}}
|
||||
{{-- const month = String(date.getMonth() + 1).padStart(2, '0');--}}
|
||||
{{-- const day = String(date.getDate()).padStart(2, '0');--}}
|
||||
{{-- const hours = String(date.getHours()).padStart(2, '0');--}}
|
||||
{{-- const minutes = String(date.getMinutes()).padStart(2, '0');--}}
|
||||
{{-- const seconds = String(date.getSeconds()).padStart(2, '0');--}}
|
||||
|
||||
{{-- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;--}}
|
||||
{{--}--}}
|
||||
|
||||
{{--function formatDuration(totalSeconds) {--}}
|
||||
{{-- const hours = Math.floor(totalSeconds / 3600);--}}
|
||||
{{-- const minutes = Math.floor((totalSeconds % 3600) / 60);--}}
|
||||
|
||||
{{-- const h = String(hours);--}}
|
||||
{{-- const i = String(minutes).padStart(2, '0');--}}
|
||||
|
||||
{{-- return `${h}:${i}`;--}}
|
||||
{{--}--}}
|
||||
|
||||
|
||||
{{--breakBtn.addEventListener('click', () => {--}}
|
||||
{{-- if (!breakStarted) {--}}
|
||||
{{-- // شروع استراحت 🚀--}}
|
||||
{{-- breakStarted = true;--}}
|
||||
{{-- breakStartTime = new Date();--}}
|
||||
|
||||
{{-- // تغییر ظاهر دکمه و شروع تایمر (همچنان M:S نمایش داده می شود)--}}
|
||||
{{-- breakBtn.classList.remove('bg-gray-500', 'hover:bg-gray-600');--}}
|
||||
{{-- breakBtn.classList.add('bg-red-500', 'hover:bg-red-600');--}}
|
||||
{{-- breakBtn.textContent = '00:00';--}}
|
||||
|
||||
{{-- let seconds = 0;--}}
|
||||
{{-- breakTimer = setInterval(() => {--}}
|
||||
{{-- seconds++;--}}
|
||||
{{-- // اینجا همچنان به صورت دقیقه:ثانیه نمایش داده می شود--}}
|
||||
{{-- const m = String(Math.floor(seconds / 60)).padStart(2, '0');--}}
|
||||
{{-- const s = String(seconds % 60).padStart(2, '0');--}}
|
||||
{{-- breakBtn.textContent = ` ${m}:${s}`;--}}
|
||||
{{-- }, 1000);--}}
|
||||
{{-- //send start time--}}
|
||||
{{-- let formData = new FormData();--}}
|
||||
{{-- formData.append('start_time', formatLocalDatetime(breakStartTime));--}}
|
||||
|
||||
{{-- formData.append('_token', '{{ csrf_token() }}');--}}
|
||||
|
||||
{{-- fetch("{{ url('attendance/break') }}", {--}}
|
||||
{{-- method: "POST",--}}
|
||||
{{-- body: formData--}}
|
||||
{{-- })--}}
|
||||
{{-- .then(res => res.json())--}}
|
||||
{{-- .then(data => console.log('✅ Break Started Logged:', data))--}}
|
||||
{{-- .catch(err => console.error('💥 Error starting break:', err));--}}
|
||||
|
||||
{{-- } else {--}}
|
||||
{{-- // پایان استراحت 🛑--}}
|
||||
{{-- breakStarted = false;--}}
|
||||
{{-- const breakEndTime = new Date();--}}
|
||||
{{-- clearInterval(breakTimer);--}}
|
||||
|
||||
{{-- // 1. محاسبه مدت زمان استراحت (بر حسب ثانیه)--}}
|
||||
{{-- const durationSec = Math.floor((breakEndTime - breakStartTime) / 1000);--}}
|
||||
|
||||
{{-- // 2. تبدیل زمان های شروع و پایان به فرمت رشته ای--}}
|
||||
{{-- const breakStartTime_str = formatLocalDatetime(breakStartTime);--}}
|
||||
{{-- const breakEndTime_str = formatLocalDatetime(breakEndTime);--}}
|
||||
|
||||
{{-- // 3. فرمت کردن مدت زمان به H:i (فقط برای نمایش در کنسول/هشدار)--}}
|
||||
{{-- const duration_formatted_h_i = formatDuration(durationSec);--}}
|
||||
|
||||
{{-- // 4. نمایش دکمه به حالت اولیه--}}
|
||||
{{-- breakBtn.classList.remove('bg-red-500', 'hover:bg-red-600');--}}
|
||||
{{-- // breakBtn.classList.add('bg-gray-500', 'hover:bg-gray-600');--}}
|
||||
{{-- breakBtn.textContent = 'استراحت';--}}
|
||||
|
||||
{{-- // 5. ارسال دادهها به سرور--}}
|
||||
{{-- let formData = new FormData();--}}
|
||||
{{-- formData.append('start_time', breakStartTime_str);--}}
|
||||
{{-- formData.append('end_time', breakEndTime_str);--}}
|
||||
{{-- formData.append('duration_seconds', durationSec);--}}
|
||||
{{-- formData.append('_token', '{{ csrf_token() }}');--}}
|
||||
|
||||
{{-- fetch("{{ url('attendance/break') }}", {--}}
|
||||
{{-- method: "POST",--}}
|
||||
{{-- body: formData--}}
|
||||
{{-- })--}}
|
||||
{{-- .then(res => res.json())--}}
|
||||
{{-- .then(data => console.log('✅ Break saved:', data))--}}
|
||||
{{-- .catch(err => console.error('💥 Error:', err));--}}
|
||||
{{-- }--}}
|
||||
{{--});--}}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{{-- ATTENDANCE MODAL--}}
|
||||
function clearTime(fieldId) {
|
||||
document.getElementById(fieldId).value = '';
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('modal-container').classList.add('hidden');
|
||||
}
|
||||
|
||||
document.getElementById('modal-container').addEventListener('click', (e) => {
|
||||
if (e.target === document.getElementById('modal-container')) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
function confirmSubmit() {
|
||||
return confirm("آیا تغییرات اعمال شوند؟");
|
||||
}
|
||||
|
||||
function openModal(title, id, enteredAt, exitedAt) {
|
||||
const isNewEntry = id === 'new';
|
||||
const modalTitleElement = document.getElementById('modal-title');
|
||||
|
||||
const hiddenIdInput = document.getElementById('edit_id');
|
||||
// const hiddenIdInput = document.getElementById('delete_id');
|
||||
|
||||
const deleteIdInput = document.getElementById('delete_id');
|
||||
const submitButton = document.querySelector('#modal-content button[type="submit"]');
|
||||
const hiddenDateInput = document.querySelector('input[name="date"]');
|
||||
|
||||
// مقداردهی به فرم حذف
|
||||
if (deleteIdInput) {
|
||||
deleteIdInput.value = id;
|
||||
}
|
||||
|
||||
const newDateLabel = document.getElementById('new-date-label');
|
||||
const newDateGroup = document.getElementById('new-date-group');
|
||||
const displayDateInput = document.getElementById('display_date');
|
||||
|
||||
const panelLink = document.getElementById('panel-access-link');
|
||||
panelLink.href = `/go_down/${id}`;
|
||||
|
||||
document.getElementById('modal-container').classList.remove('hidden');
|
||||
|
||||
const startTimeInput = document.querySelector('input[name="starttime"]');
|
||||
const endTimeInput = document.querySelector('input[name="endtime"]');
|
||||
|
||||
// تنظیم ساعتها
|
||||
if (enteredAt && !isNaN(new Date(enteredAt).getTime())) {
|
||||
startTimeInput.value = new Date(enteredAt).toLocaleTimeString('en-GB', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
} else {
|
||||
startTimeInput.value = '09:00';
|
||||
}
|
||||
|
||||
if (exitedAt && !isNaN(new Date(exitedAt).getTime())) {
|
||||
endTimeInput.value = new Date(exitedAt).toLocaleTimeString('en-GB', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
// else {
|
||||
// endTimeInput.value = '17:30';
|
||||
// }
|
||||
|
||||
|
||||
if (isNewEntry) {
|
||||
// حالت ثبت جدید
|
||||
modalTitleElement.innerText = 'ثبت روز کاری جدید';
|
||||
newDateLabel.style.display = 'block';
|
||||
newDateGroup.style.display = 'block';
|
||||
|
||||
displayDateInput.value = '';
|
||||
hiddenDateInput.value = '';
|
||||
|
||||
// ✅ اصلاح: مقداردهی 'new' به فیلد ID
|
||||
if (hiddenIdInput) hiddenIdInput.value = 'new';
|
||||
|
||||
submitButton.onclick = function () {
|
||||
if (hiddenDateInput.value === '') {
|
||||
alert("لطفاً یک تاریخ را از تقویم انتخاب کنید.");
|
||||
return false;
|
||||
}
|
||||
return confirmSubmit();
|
||||
};
|
||||
|
||||
} else {
|
||||
// حالت ویرایش
|
||||
modalTitleElement.innerText = 'تغییر ساعت کاری';
|
||||
newDateLabel.style.display = 'none';
|
||||
newDateGroup.style.display = 'none';
|
||||
|
||||
// ✅ اصلاح: استفاده از متغیر 'id' که از جدول فرستاده شده است
|
||||
if (hiddenIdInput) hiddenIdInput.value = id;
|
||||
if (hiddenDateInput) hiddenDateInput.value = enteredAt;
|
||||
|
||||
submitButton.onclick = function () {
|
||||
return confirmSubmit();
|
||||
};
|
||||
}
|
||||
|
||||
//date and clock picker
|
||||
$('.clockpicker').clockpicker({
|
||||
autoclose: false,
|
||||
placement: 'center',
|
||||
donetext: 'ثبت',
|
||||
vibrate: true,
|
||||
});
|
||||
|
||||
// --- فعالسازی تقویم ---
|
||||
$(".example1").persianDatepicker({
|
||||
format: "YYYY/MM/DD",
|
||||
altField: '#date',
|
||||
altFormat: 'YYYY-MM-DD',
|
||||
calendar: {
|
||||
persian: {
|
||||
locale: 'fa',
|
||||
leapYearMode: "astronomical"
|
||||
}
|
||||
},
|
||||
observer: true,
|
||||
initialValue: false,
|
||||
initialValueType: 'gregorian',
|
||||
autoClose: true,
|
||||
toolbox: {
|
||||
calendarSwitch: {enabled: false},
|
||||
todayButton: {enabled: true, text: {fa: "امروز"}}
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
// 1. فعالسازی Clockpicker
|
||||
$('.clockpicker').clockpicker({
|
||||
autoclose: false,
|
||||
placement: 'center',
|
||||
donetext: 'ثبت',
|
||||
vibrate: true,
|
||||
// donetext: true,
|
||||
});
|
||||
|
||||
|
||||
$(".example1").persianDatepicker({
|
||||
format: "YYYY/MM/DD",
|
||||
altField: '#date',
|
||||
altFormat: 'YYYY-MM-DD',
|
||||
calendar: {
|
||||
persian: {
|
||||
locale: 'fa',
|
||||
leapYearMode: "astronomical"
|
||||
}
|
||||
},
|
||||
observer: true,
|
||||
initialValue: false,
|
||||
initialValueType: 'gregorian',
|
||||
autoClose: true,
|
||||
toolbox: {
|
||||
calendarSwitch: {enabled: false},
|
||||
todayButton: {enabled: true, text: {fa: "امروز"}}
|
||||
},
|
||||
onSelect: function (unix) {
|
||||
// تبدیل تاریخ انتخابی به میلادی درست با timezone ایران
|
||||
const d = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
||||
console.log("Gregorian (Fixed):", d.format("YYYY-MM-DD"));
|
||||
}
|
||||
});
|
||||
|
||||
// 2. فعالسازی Persian Datepicker
|
||||
// $('.example1').pDatepicker({
|
||||
// format: 'YYYY/MM/DD',
|
||||
// // placement: 'center',
|
||||
// altField: '#date', // این فیلد مخفی، تاریخ میلادی تبدیل شده را دریافت میکند
|
||||
// altFormat: 'YYYY-MM-DD', // میلادی ذخیره بشه
|
||||
// // changeMonth: true,
|
||||
// // changeYear: true,
|
||||
// // showOtherMonths: true,
|
||||
// // selectOtherMonths: true,
|
||||
// autoClose: true,
|
||||
//
|
||||
// });
|
||||
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,91 +1,247 @@
|
||||
<?php
|
||||
|
||||
use App\Models\App;
|
||||
use App\Models\User;
|
||||
use App\Models\Tasks;
|
||||
|
||||
$users = User::get();
|
||||
$apps = App::get();
|
||||
|
||||
if (isset($task)) {
|
||||
$selectedAssignees = [];
|
||||
if ($task && $task->assignees) {
|
||||
$decoded = json_decode($task->assignees, true);
|
||||
$selectedAssignees = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
@extends('app')
|
||||
@section('content')
|
||||
<div class="w-full mt-[90px] justify-center container flex ">
|
||||
<div class="bg-white w-[350px] p-6 rounded-xl ">
|
||||
<h1 class="w-full bg-cyan-500 rounded-t-xl text-center p-1.5 font-bold text-slate-50">ایجاد وظیفه جدید</h1>
|
||||
<form action="{{Route('create_api')}}" method="POST" class="grid grid-cols-2 gap-4 items-center">
|
||||
<div class="bg-white w-[350px] rounded-xl ">
|
||||
<h1 class="w-[350px] bg-cyan-500 rounded-t-xl text-center p-1.5 font-bold text-slate-50">{{isset($task) ? "ویرایش وظیفه" : "ایجاد وظیفه جدید"}}
|
||||
</h1>
|
||||
<form action="{{isset($task) ? Route('task.edit') : Route('task.create')}}" method="POST" class=" p-6 grid-cols-2 gap-4 items-center">
|
||||
@csrf
|
||||
<!-- نام Issue (الزامی) -->
|
||||
<label for="name" class="font-medium text-sm">نام:</label>
|
||||
<input class="rounded-lg border border-gray-300 p-2 w-full" type="text" id="name" name="name" required>
|
||||
@if(isset($task))
|
||||
<input type="hidden" id="id" name="id" value="{{$task->id}}">
|
||||
@endif
|
||||
<label for="title" class="font-medium text-sm"><span class="text-red-500">*</span>عنوان:</label>
|
||||
<input class="rounded-lg border border-gray-300 p-2 w-full" value="{{$task->title ?? null}}" type="text"
|
||||
id="title" name="title" required>
|
||||
|
||||
<!-- توضیحات HTML -->
|
||||
<label for="description_html" class="font-medium text-sm">توضیحات:</label>
|
||||
<textarea id="description_html" name="description_html"
|
||||
class="rounded-lg border border-gray-300 p-2 w-full"></textarea>
|
||||
<label for="description" class="font-medium text-sm">توضیحات:</label>
|
||||
<textarea id="description" name="description"
|
||||
class="rounded-lg border border-gray-300 p-2 w-full">{{$task->description ?? null}}</textarea>
|
||||
|
||||
<!-- اولویت -->
|
||||
<label for="priority" class="font-medium text-sm">اولویت:</label>
|
||||
<select id="priority" name="priority"
|
||||
class="rounded-lg border border-gray-300 p-2 w-full">
|
||||
<option value="none">ندارد</option>
|
||||
<option value="urgent">فوری</option>
|
||||
<option value="high">بالا</option>
|
||||
<option value="medium">عادی</option>
|
||||
<option value="low">پایین</option>
|
||||
<select id="priority" name="priority" class="rounded-lg border border-gray-300 p-2 w-full">
|
||||
<option value="none" {{ old('priority', $task->priority ?? '') == 'none' ? 'selected' : '' }}>ندارد</option>
|
||||
<option value="low" {{ old('priority', $task->priority ?? '') == 'low' ? 'selected' : '' }}>کم</option>
|
||||
<option value="medium" {{ old('priority', $task->priority ?? '') == 'medium' ? 'selected' : '' }}>عادی</option>
|
||||
<option value="urgent" {{ old('priority', $task->priority ?? '') == 'urgent' ? 'selected' : '' }}>فوری</option>
|
||||
<option value="high" {{ old('priority', $task->priority ?? '') == 'high' ? 'selected' : '' }}>زیاد</option>
|
||||
</select>
|
||||
|
||||
<!-- تاریخ شروع -->
|
||||
<label for="start_date" class="font-medium text-sm">تاریخ شروع:</label>
|
||||
<input class="rounded-lg border border-gray-300 p-2 w-full" type="date" id="start_date"
|
||||
name="start_date">
|
||||
<div class="p-2">
|
||||
<label for="start_date" class="font-medium text-sm">تاریخ شروع <span
|
||||
class="text-red-500">*</span></label>
|
||||
<input type="text" id="start_date" name="start_date_display"
|
||||
class="rounded-lg border border-gray-300 p-2 " value="{{$task->start_date ?? null}}"
|
||||
placeholder="تاریخ شروع" autocomplete="off" readonly required>
|
||||
<input type="hidden" name="start_date" id="start_date_gregorian">
|
||||
|
||||
<!-- تاریخ هدف -->
|
||||
<label for="target_date" class="font-medium text-sm">تاریخ هدف:</label>
|
||||
<input class="rounded-lg border border-gray-300 p-2 w-full" type="date" id="target_date"
|
||||
name="target_date">
|
||||
<!-- تاریخ هدف -->
|
||||
<label for="target_date" class="font-medium text-sm">تاریخ هدف</label>
|
||||
{{-- <div class="relative">--}}
|
||||
<input type="text" id="target_date" name="target_date_display"
|
||||
class="rounded-lg border border-gray-300 p-2 pwt-datepicker-input-element mt-2"
|
||||
value="{{$task->target_date ?? null}}"
|
||||
placeholder="تاریخ هدف" autocomplete="off" readonly>
|
||||
<button type="button" id="clear_target_date"
|
||||
class="absolute left-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-red-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
{{-- </div>--}}
|
||||
<input type="hidden" name="target_date" id="target_date_gregorian">
|
||||
</div>
|
||||
|
||||
<!-- وضعیت -->
|
||||
<label for="state" class="font-medium text-sm">وضعیت:</label>
|
||||
<select id="state" name="state"
|
||||
class="rounded-lg border border-gray-300 p-2 w-full">
|
||||
<option value="c73c2044-ea14-42cb-b08d-cecf3d1fdfb5">درحال انجام</option>
|
||||
<option value="a9ce312f-cfca-491e-8e3d-1975aa6ebc10">در انتظار</option>
|
||||
<option value="dea1fb57-a281-4fe7-8f3e-ee5567637e35">دارای خطا</option>
|
||||
<option value="770fbe42-99f6-41db-9b9e-773d9cced1a5">در صف انجام</option>
|
||||
<option value="b0e464e1-34eb-41ea-b2f7-3ef7483bc9f0">انجامشده</option>
|
||||
<option value="d61d14a3-72f7-4f44-894b-efe8b2a75be1">لغوشده</option>
|
||||
<label for="state" class="font-medium text-sm"><span class="text-red-500">*</span>وضعیت:</label>
|
||||
<select id="state" name="state" class="rounded-lg border border-gray-300 p-2 w-full" required>
|
||||
<option value="1" {{ old('state', $task->state ?? '') == 1 ? 'selected' : '' }}>در انتظار</option>
|
||||
<option value="2" {{ old('state', $task->state ?? '') == 2 ? 'selected' : '' }}>در صف انجام</option>
|
||||
<option value="3" {{ old('state', $task->state ?? '') == 3 ? 'selected' : '' }}>درحال انجام</option>
|
||||
<option value="4" {{ old('state', $task->state ?? '') == 4 ? 'selected' : '' }}>انجامشده</option>
|
||||
</select>
|
||||
|
||||
<!-- کاربران مسئول -->
|
||||
{{-- <label for="assignees" class="font-medium text-sm">انتصاب کاربران:</label>--}}
|
||||
<label class="font-medium text-sm">انتصاب کاربران:</label>
|
||||
<div class="rounded-lg border border-gray-300 p-2 w-full max-h-32 overflow-y-auto space-y-2">
|
||||
@foreach($users as $user)
|
||||
<label class="flex items-center space-x-2">
|
||||
<input type="checkbox" name="assignees[]" value="{{ $user->plain_id }}" class="rounded text-cyan-600 focus:ring-cyan-500">
|
||||
<span>{{ $user->name }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
<label for="assignees" class="font-medium text-sm">انتصاب
|
||||
کاربران:</label>
|
||||
<div class="relative" id="dropdown-container">
|
||||
{{-- دکمه --}}
|
||||
<button type="button" id="dropdown-btn"
|
||||
class="w-full bg-white border border-gray-300 rounded-lg px-4 py-2 text-right flex justify-between items-center">
|
||||
<span id="selected-count">انتخاب کاربران</span>
|
||||
<svg class="w-4 h-4 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div id="dropdown-list"
|
||||
class="hidden absolute z-20 mt-1 w-full bg-white border border-gray-300 rounded-lg shadow-lg">
|
||||
|
||||
<div class="p-2 border-b">
|
||||
<input type="text" id="search-input"
|
||||
class="w-full px-3 py-1 border border-gray-200 rounded-lg text-sm"
|
||||
placeholder="جستجوی کاربر...">
|
||||
</div>
|
||||
|
||||
{{-- لیست کاربران --}}
|
||||
<div id="users-list" class="max-h-48 overflow-y-auto space-y-1 ">
|
||||
@foreach($users as $user)
|
||||
<label class="user-item flex items-center gap-2 p-1 hover:bg-gray-50 rounded cursor-pointer"
|
||||
data-name="{{ $user->name }}">
|
||||
<input type="checkbox"
|
||||
name="assignees[]"
|
||||
value="{{ $user->id }}"
|
||||
class="user-checkbox rounded text-cyan-600"
|
||||
{{ isset($task) && (in_array($user->id, $selectedAssignees)) ? 'checked' : '' }}>
|
||||
|
||||
<span>{{ $user->name }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- برچسبها -->
|
||||
<label for="labels" class="font-medium text-sm">برچسبها:</label>
|
||||
<input class="rounded-lg border border-gray-300 p-2 w-full"
|
||||
type="text" id="labels" name="labels[]" placeholder="UUID1, UUID2">
|
||||
{{-- <!-- برچسبها -->--}}
|
||||
{{-- <label for="labels" class="font-medium text-sm">برچسبها:</label>--}}
|
||||
{{-- <input class="rounded-lg border border-gray-300 p-2 w-full"--}}
|
||||
{{-- type="text" id="labels" name="labels[]" placeholder="UUID1, UUID2">--}}
|
||||
|
||||
<!-- Issue والد -->
|
||||
<label for="parent" class="font-medium text-sm">پروژه والد:</label>
|
||||
<select id="parent" name="parent" class="rounded-lg border border-gray-300 p-2 w-full">
|
||||
@foreach($tasks as $task)
|
||||
<option value="{{$task->task_id}}">{{$task->name}}</option>
|
||||
@endforeach
|
||||
<!-- اپ -->
|
||||
<label for="app" class="font-medium text-sm">نام اپ:</label>
|
||||
<select id="app" name="app" class="rounded-lg z-20 border border-gray-300 p-2 w-full">
|
||||
<option value="">-- انتخاب اپ --</option>
|
||||
@if(!empty($apps) && count($apps) > 0)
|
||||
@foreach($apps as $app)
|
||||
<option value="{{ $app->id }}" {{ old('app', $task->app ?? '') == $app->id ? 'selected' : '' }}>
|
||||
{{ $app->name_fa }}
|
||||
</option>
|
||||
@endforeach
|
||||
@endif
|
||||
</select>
|
||||
|
||||
<!-- امتیاز تخمین -->
|
||||
<label for="estimate_point" class="font-medium text-sm">Estimate Point (0-7):</label>
|
||||
<input class="rounded-lg border border-gray-300 p-2 w-full" type="number" id="estimate_point"
|
||||
name="estimate_point" min="0" max="7">
|
||||
|
||||
<!-- دکمه -->
|
||||
<div></div>
|
||||
<div class="inline-block flex pt-3 justify-center">
|
||||
<button type="submit"
|
||||
class="bg-cyan-600 text-white py-2 px-4 rounded-lg hover:bg-cyan-700 transition">
|
||||
ایجاد وظیفه
|
||||
</button>
|
||||
class="bg-cyan-600 text-white py-2 px-4 rounded-lg hover:bg-cyan-700 transition <!--mr-24 mt-2.5-->">
|
||||
{{isset($task) ? "ویرایش" : "ایجاد وظیفه جدید"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const dropdownBtn = document.getElementById('dropdown-btn');
|
||||
const dropdownList = document.getElementById('dropdown-list');
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const usersList = document.getElementById('users-list');
|
||||
const selectedCountSpan = document.getElementById('selected-count');
|
||||
|
||||
// باز و بسته کردن
|
||||
dropdownBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
dropdownList.classList.toggle('hidden');
|
||||
if (!dropdownList.classList.contains('hidden')) {
|
||||
searchInput.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// بستن با کلیک بیرون
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!dropdownBtn.contains(e.target) && !dropdownList.contains(e.target)) {
|
||||
dropdownList.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// قابلیت جستجو
|
||||
searchInput.addEventListener('input', function () {
|
||||
const searchText = this.value.toLowerCase();
|
||||
const items = usersList.querySelectorAll('.user-item');
|
||||
|
||||
items.forEach(item => {
|
||||
const name = item.getAttribute('data-name').toLowerCase();
|
||||
if (name.includes(searchText)) {
|
||||
item.style.display = 'flex';
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// آپدیت تعداد
|
||||
function updateSelectedCount() {
|
||||
const selected = document.querySelectorAll('.user-checkbox:checked');
|
||||
const count = selected.length;
|
||||
selectedCountSpan.textContent = count === 0 ? 'انتخاب کاربران' : count + ' کاربر انتخاب شده';
|
||||
}
|
||||
|
||||
document.querySelectorAll('.user-checkbox').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', updateSelectedCount);
|
||||
});
|
||||
|
||||
updateSelectedCount();
|
||||
|
||||
$(document).ready(function () {
|
||||
// تاریخ شروع با ساعت جاری
|
||||
$('#start_date').persianDatepicker({
|
||||
format: 'YYYY/MM/DD',
|
||||
autoClose: true,
|
||||
toolbox: {
|
||||
calendarSwitch: {enabled: false},
|
||||
todayButton: {enabled: true, text: {fa: 'امروز'}}
|
||||
},
|
||||
onSelect: function (unix) {
|
||||
// تبدیل به میلادی
|
||||
let gregorian = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
||||
let dateStr = gregorian.format('YYYY-MM-DD');
|
||||
$('#start_date_gregorian').val(dateStr);
|
||||
console.log('Start Gregorian:', dateStr);
|
||||
}
|
||||
});
|
||||
|
||||
// تاریخ هدف
|
||||
$('#target_date').persianDatepicker({
|
||||
format: 'YYYY/MM/DD',
|
||||
autoClose: true,
|
||||
toolbox: {
|
||||
calendarSwitch: {enabled: false},
|
||||
todayButton: {enabled: true, text: {fa: 'امروز'}}
|
||||
},
|
||||
onSelect: function (unix) {
|
||||
let gregorianDate = new persianDate(unix).toCalendar('gregorian').toLocale('en');
|
||||
let dateStrTarget = gregorianDate.format('YYYY-MM-DD');
|
||||
// let dateTimeStr = `${dateStr} 23:59:59`;
|
||||
$('#target_date_gregorian').val(dateStrTarget);
|
||||
console.log('Target DateTime (Gregorian):', dateStrTarget);
|
||||
}
|
||||
});
|
||||
|
||||
// دکمه پاک کردن تاریخ هدف
|
||||
$('#clear_target_date').on('click', function () {
|
||||
$('#target_date').val('');
|
||||
$('#target_date_gregorian').val('');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
<figure class="attachment attachment--content">
|
||||
{!! trim($content->renderTrixContentAttachment($options)) !!}
|
||||
</figure>
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="not-prose attachment-gallery attachment-gallery--{{ count($attachables) }}">
|
||||
@foreach ($attachables as $attachable)
|
||||
@include('rich-text-laravel::attachables._remote_image', [
|
||||
'remoteImage' => $attachable,
|
||||
'gallery' => true
|
||||
])
|
||||
@endforeach
|
||||
</div>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<span>⊠</span>
|
||||
@@ -0,0 +1,11 @@
|
||||
<figure class="not-prose attachment attachment--file attachment--{{ $remoteFile->extension() }}">
|
||||
<figcaption class="attachment__caption">
|
||||
@if ($remoteFile->caption)
|
||||
{{ $remoteFile->caption }}
|
||||
@else
|
||||
<span class="attachment__name">{{ $remoteFile->filename }}</span>
|
||||
<span class="attachment__size">{{ $remoteFile->filesizeForHumans() }}</span>
|
||||
<span class="attachment__download"><a class="!underline" href="{{ $remoteFile->url }}">{{ __('Download') }}</a></span>
|
||||
@endif
|
||||
</figcaption>
|
||||
</figure>
|
||||
@@ -0,0 +1,8 @@
|
||||
<figure class="not-prose attachment attachment--preview attachment--{{ $remoteImage->extension() }}" @if ($gallery ?? false) data-trix-attributes='{"presentation":"gallery"}' @endif>
|
||||
<img src="{{ $remoteImage->url }}" width="{{ $remoteImage->width }}" height="{{ $remoteImage->height }}" />
|
||||
@if ($remoteImage->caption)
|
||||
<figcaption class="attachment__caption">
|
||||
{{ $remoteImage->caption }}
|
||||
</figcaption>
|
||||
@endif
|
||||
</figure>
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
<div class="attachment-gallery attachment-gallery--{{ $attachmentGallery->count() }}">
|
||||
@foreach ($attachmentGallery->attachments() as $attachment)
|
||||
{!! $attachment->richTextRender() !!}
|
||||
@endforeach
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
@props(['id', 'name' => null, 'value' => ''])
|
||||
|
||||
@php
|
||||
$editor = \Tonysm\RichTextLaravel\RichTextLaravel::editorName();
|
||||
@endphp
|
||||
|
||||
<x-dynamic-component
|
||||
:component="'rich-text::' . $editor"
|
||||
:id="$id"
|
||||
:name="$name"
|
||||
:value="$value"
|
||||
{{ $attributes }}
|
||||
>{{ $slot }}</x-dynamic-component>
|
||||
@@ -0,0 +1,23 @@
|
||||
@props(['id', 'name' => null, 'value' => ''])
|
||||
|
||||
<div
|
||||
{{ $attributes->whereDoesntStartWith('wire:') }}
|
||||
@if ($attributes->has('wire:model'))
|
||||
x-data="{ content: $wire.entangle('{{ $attributes->wire('model')->value() }}') }"
|
||||
x-on:lexxy:initialize="$nextTick(() => $refs.input.value = content)"
|
||||
x-on:lexxy:change="content = $refs.input.value"
|
||||
@endif
|
||||
>
|
||||
<lexxy-editor
|
||||
id="{{ $id }}"
|
||||
class="lexxy-content"
|
||||
value="{{ $value }}"
|
||||
@if ($name ?? false)
|
||||
name="{{ $name }}"
|
||||
@endif
|
||||
@if ($attributes->has('wire:model'))
|
||||
wire:ignore
|
||||
x-ref="input"
|
||||
@endif
|
||||
>{{ $slot }}</lexxy-editor>
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
@props(['theme' => 'default'])
|
||||
|
||||
@inject('assets', \Tonysm\RichTextLaravel\AssetsManager::class)
|
||||
|
||||
@if (config('rich-text-laravel.editor') === 'lexxy')
|
||||
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/lexxy.css') }}" />
|
||||
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/lexxy-rich-text-laravel-attachments.css') }}" />
|
||||
@if ($theme === 'daisyui')
|
||||
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/lexxy-rich-text-laravel-daisyui.css') }}" />
|
||||
@elseif ($theme === 'flux')
|
||||
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/lexxy-rich-text-laravel-flux.css') }}" />
|
||||
@endif
|
||||
@elseif ($theme === 'richtextlaravel')
|
||||
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/trix-rich-text-laravel.css') }}" />
|
||||
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/trix-rich-text-laravel-attachments.css') }}" />
|
||||
@else
|
||||
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/trix.css') }}" />
|
||||
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/trix-rich-text-laravel-attachments.css') }}" />
|
||||
@if ($theme === 'daisyui')
|
||||
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/trix-rich-text-laravel-daisyui.css') }}" />
|
||||
@elseif ($theme === 'flux')
|
||||
<link {{ $attributes }} rel="stylesheet" href="{{ $assets->url('/trix-rich-text-laravel-flux.css') }}" />
|
||||
@endif
|
||||
@endif
|
||||
@@ -0,0 +1,41 @@
|
||||
@props(['id', 'name' => null, 'value' => ''])
|
||||
|
||||
<div
|
||||
{{ $attributes->whereDoesntStartWith('wire:') }}
|
||||
@if ($attributes->has('wire:model'))
|
||||
x-data="{ content: $wire.entangle('{{ $attributes->wire('model')->value() }}') }"
|
||||
x-on:trix-initialize="$refs.input.value = content"
|
||||
x-on:trix-change="content = $refs.input.value"
|
||||
@endif
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
@if ($name ?? false)
|
||||
name="{{ $name }}"
|
||||
@endif
|
||||
id="{{ $id }}_input"
|
||||
value="{{ $value }}"
|
||||
@if ($attributes->has('wire:model'))
|
||||
x-on:change="$refs.input.value = $event.target.value"
|
||||
{{ $attributes->whereStartsWith('wire:') }}
|
||||
@endif
|
||||
/>
|
||||
|
||||
<trix-toolbar
|
||||
id="{{ $id }}_toolbar"
|
||||
@if ($attributes->has('wire:model'))
|
||||
wire:ignore
|
||||
@endif
|
||||
></trix-toolbar>
|
||||
|
||||
<trix-editor
|
||||
id="{{ $id }}"
|
||||
toolbar="{{ $id }}_toolbar"
|
||||
input="{{ $id }}_input"
|
||||
class="trix-content"
|
||||
@if ($attributes->has('wire:model'))
|
||||
x-ref="input"
|
||||
wire:ignore
|
||||
@endif
|
||||
></trix-editor>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
<div class="{{ e(\Tonysm\RichTextLaravel\RichTextLaravel::editorName()) . '-content' }}">
|
||||
@if (trim($content = $content->renderWithAttachments()))
|
||||
{!! $content !!}
|
||||
@endif
|
||||
</div>
|
||||
Reference in New Issue
Block a user