initial commit - code moved to separate repo

This commit is contained in:
Dobromir Popov
2024-02-22 04:19:38 +02:00
commit 560d503219
240 changed files with 105125 additions and 0 deletions

View File

@ -0,0 +1,101 @@
import Link from "next/link";
import { Publisher } from "@prisma/client"
// import {IsDateXMonthsAgo} from "../../helpers/const"
import { useEffect, useState } from 'react'
import toast from "react-hot-toast";
import axiosInstance from '../../src/axiosSecure';
//add months to date. works with negative numbers and numbers > 12
export function addMonths(numOfMonths, date) {
var date = new Date(date);
var m, d = (date = new Date(+date)).getDate();
date.setMonth(date.getMonth() + numOfMonths, 1);
m = date.getMonth();
date.setDate(d);
if (date.getMonth() !== m) date.setDate(0);
return date;
}
//is date in range of months from and to
//usage:
//is date in last month: IsDateInXMonths(date, -1, 0)
//is date in current month: IsDateInXMonths(date, 0, 0)
//is date in next month: IsDateInXMonths(date, 0, 1)
export function IsDateInXMonths(date, monthsFrom, monthsTo) {
var date = new Date(date);
var dateYearMonth = new Date(date.getFullYear(), date.getMonth(), 1);
if (monthsFrom === undefined) monthsFrom = -100;
if (monthsTo === undefined) monthsTo = 100;
// var from = new Date(date.setMonth(dateYearMonth.getMonth()+monthsFrom));
// var to = new Date(date.setMonth(dateYearMonth.getMonth()+monthsTo));
var from = addMonths(monthsFrom, dateYearMonth);
var to = addMonths(monthsTo, dateYearMonth);
//is date between from and to
return date >= from && date <= to;
};
export default function PublisherCard({ publisher }) {
const [isCardVisible, setIsCardVisible] = useState(true);
const handleDelete = async (id) => {
try {
console.log("card: deleting publisher = ", id, "url: ", `/api/data/publishers/${id}`);
const response = await axiosInstance.delete(`/api/data/publishers/${id}`);
if (response.status === 200) {
document.getElementById(`publisher-card-${id}`).classList.add('cardFadeOut');
setTimeout(() => setIsCardVisible(false), 300);
}
} catch (error) {
console.log(JSON.stringify(error));
}
};
return isCardVisible ? (
// className="block p-6 max-w-sm bg-white rounded-lg border border-gray-200 shadow-md hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700 mb-3"
<div id={`publisher-card-${publisher.id}`} className={`relative block p-6 max-w-sm rounded-lg border border-gray-200 shadow-md hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700 mb-3
${publisher.isImported ? "bg-orange-50" : (publisher.isTrained ? "bg-white" : "bg-red-50")}`}>
<a
href={`/cart/publishers/edit/${publisher.id}`}
className=""
key={publisher.id}
>
<h5 className="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
{publisher.firstName} {publisher.lastName} ({publisher.isactive ? "active" : "inactive"})
</h5>
<div className="font-normal text-gray-700 dark:text-gray-200">
<p> {publisher.assignments.length} смени общо</p>
<p> достъпност: {publisher.availabilities?.length}</p>
{/* <p> {publisher.shifts.filter(s => IsDateInXMonths(s.startTime, -1, 0)).length} last month</p>
<p> {publisher.shifts.filter(s => IsDateInXMonths(s.startTime, 0, 0)).length} this month</p>
<p> {publisher.shifts.filter(s => IsDateInXMonths(s.startTime, 0, 1)).length} next month</p> */}
</div>
</a>
<div className="absolute bottom-2 right-2">
<button onClick={() => handleDelete(publisher.id)} aria-label="Delete Publisher">
<svg className="w-5 h-6 text-red-500 hover:text-red-700" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path d="M10 11V17" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M14 11V17" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M4 7H20" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M6 7H12H18V18C18 19.6569 16.6569 21 15 21H9C7.34315 21 6 19.6569 6 18V7Z" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M9 5C9 3.89543 9.89543 3 11 3H13C14.1046 3 15 3.89543 15 5V7H9V5Z" stroke="#000000" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
{/* <path d="M8 9a1 1 0 000 2h4a1 1 0 100-2H8z" />
<path fillRule="evenodd" d="M4.293 4.293A1 1 0 015.707 3.707L10 8l4.293-4.293a1 1 0 111.414 1.414L11.414 9l4.293 4.293a1 1 0 01-1.414 1.414L10 10.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 9 4.293 4.707a1 1 0 010-1.414z" clipRule="evenodd" /> */}
</svg>
</button>
</div>
<style jsx>{`
.cardFadeOut {
transition: opacity 0.3s ease, transform 0.3s ease;
opacity: 0;
transform: scale(0.8);
}
`}</style>
</div>
) : null;
}

View File

@ -0,0 +1,317 @@
// import axios from "axios";
import React, { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { useRouter } from "next/router";
import Link from "next/link";
import axiosInstance from '../../src/axiosSecure';
//import { getDate } from "date-fns";
import { monthNamesBG, GetTimeFormat, GetDateFormat } from "../../src/helpers/const"
import PublisherSearchBox from './PublisherSearchBox';
import AvailabilityList from "../availability/AvailabilityList";
import ShiftsList from "../publisher/ShiftsList.tsx";
import common from "../../src/helpers/common";
import ProtectedRoute from '../../components/protectedRoute';
import { UserRole } from "@prisma/client";
// import { Tabs, List } from 'tw-elements'
// model Publisher {
// id String @id @default(cuid())
// firstName String
// lastName String
// email String @unique
// phone String?
// isactive Boolean @default(true)
// isImported Boolean @default(false)
// age Int?
// availabilities Availability[]
// assignments Assignment[]
// emailVerified DateTime?
// accounts Account[]
// sessions Session[]
// role UserRole @default(USER)
// desiredShiftsPerMonth Int @default(4)
// isMale Boolean @default(true)
// isNameForeign Boolean @default(false)
// familyHeadId String? // Optional familyHeadId for each family member
// familyHead Publisher? @relation("FamilyMember", fields: [familyHeadId], references: [id])
// familyMembers Publisher[] @relation("FamilyMember")
// type PublisherType @default(Publisher)
// Town String?
// Comments String?
// }
Array.prototype.groupBy = function (prop) {
return this.reduce(function (groups, item) {
const val = item[prop]
groups[val] = groups[val] || []
groups[val].push(item)
return groups
}, {})
}
export default function PublisherForm({ item, me }) {
const router = useRouter();
console.log("init PublisherForm: ");
const urls = {
apiUrl: "/api/data/publishers/",
indexUrl: "/cart/publishers"
}
const [helpers, setHelper] = useState(null);
const fetchModules = async () => {
const h = (await import("../../src/helpers/const.js")).default;
//console.log("fetchModules: " + JSON.stringify(h));
setHelper(h);
}
useEffect(() => {
fetchModules();
}, []);
const [publisher, set] = useState(item || {
isactive: true,
});
const handleChange = ({ target }) => {
if (target.type === "checkbox") {
set({ ...publisher, [target.name]: target.checked });
} else if (target.type === "number") {
set({ ...publisher, [target.name]: parseInt(target.value) });
} else {
set({ ...publisher, [target.name]: target.value });
}
if (item?.firstName) {
publisher.isMale = item.firstName && item.firstName.endsWith('а') ? false : true;
}
}
const handleParentSelection = (head) => {
//setSelectedParent(parent);
// Update the publisher state with the selected publisher's ID
console.log("handleParentSelection: " + JSON.stringify(head));
set({ ...publisher, familyHeadId: head.id });
// Create a new object excluding the familyHeadId property
};
const handleSubmit = async (e) => {
router.query.id = router.query.id || "";
console.log("handleSubmit: " + JSON.stringify(publisher));
console.log("urls.apiUrl + router.query.id: " + urls.apiUrl + router.query.id)
e.preventDefault();
//remove availabilities, assignments from publisher
publisher.availabilities = undefined;
publisher.assignments = undefined;
let { familyHeadId, userId, ...rest } = publisher;
// Set the familyHead relation based on the selected head
const familyHeadRelation = familyHeadId ? { connect: { id: familyHeadId } } : { disconnect: true };
const userRel = userId ? { connect: { id: userId } } : { disconnect: true };
// Return the new state without familyHeadId and with the correct familyHead relation
rest = {
...rest,
familyHead: familyHeadRelation,
user: userRel
};
try {
if (router.query?.id) {
await axiosInstance.put(urls.apiUrl + router.query.id, {
...rest,
});
toast.success("Task Updated", {
position: "bottom-center",
});
} else {
await axiosInstance.post(urls.apiUrl, publisher);
toast.success("Task Saved", {
position: "bottom-center",
});
}
router.push(urls.indexUrl);
} catch (error) {
console.log(JSON.stringify(error));
//toast.error(error.response.data.message);
}
};
const handleDelete = async (e) => {
e.preventDefault();
try {
//console.log("deleting publisher id = ", router.query.id, "; url=" + urls.apiUrl + router.query.id);
await axiosInstance.delete(urls.apiUrl + router.query.id);
toast.success("Записът изтрит", {
position: "bottom-center",
});
router.push(urls.indexUrl);
} catch (error) {
console.log(JSON.stringify(error));
toast.error(error.response.data.message);
}
};
let formTitle;
me = common.parseBool(me);
if (me) {
formTitle = "Моят профил / Настройки";
} else if (router.query?.id) {
formTitle = "Редактирай вестител"; // "Edit Publisher"
} else {
formTitle = "Създай вестител"; // "Create Publisher"
}
return (
<>
<div className="flex flex-col">
<h3 className="text-2xl font-semibold mt-6 mb-4">{formTitle}</h3>
<div className="h-4"></div>
<div className="flex flex-row">
<form className="form"
onSubmit={handleSubmit}>
<div className="mb-4">
<label className="label" htmlFor="firstName">Име</label>
<input type="text" name="firstName" value={publisher.firstName} onChange={handleChange} className="textbox" placeholder="First Name" autoFocus />
</div>
<div className="mb-4">
<label className="label" htmlFor="lastName">Фамилия</label>
<input type="text" name="lastName" value={publisher.lastName} onChange={handleChange} className="textbox" placeholder="Last Name" autoFocus />
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage=" ">
<div className="form-check">
<input className="checkbox" type="checkbox" value={publisher.isNameForeign} id="isNameForeign" name="isNameForeign" onChange={handleChange} checked={publisher.isNameForeign} autoComplete="off" />
<label className="label" htmlFor="isNameForeign">
Чуждестранна фамилия</label>
</div>
</ProtectedRoute>
</div>
{/* //desiredShiftsPerMonth */}
<div className="mb-4">
<label className="label" htmlFor="desiredShiftsPerMonth">Желани смeни на месец</label>
<input type="number" name="desiredShiftsPerMonth" value={publisher.desiredShiftsPerMonth} onChange={handleChange} className="textbox" placeholder="desiredShiftsPerMonth" autoFocus />
</div>
<div className="mb-4">
<label className="label" htmlFor="email">Имейл</label>
<input type="text" name="email" value={publisher.email} onChange={handleChange} className="textbox" placeholder="Email" autoFocus />
</div>
<div className="mb-4">
<label className="label" htmlFor="phone">Телефон</label>
<input type="text" name="phone" value={publisher.phone} onChange={handleChange} className="textbox" placeholder="Phone" autoFocus />
</div>
<div className="mb-4">
<label className="label" htmlFor="parentPublisher">
Семейство (избери главата на семейството)
</label>
<PublisherSearchBox selectedId={publisher.familyHeadId} onChange={handleParentSelection} />
</div>
<div className="mb-4">
<div className="flex items-center space-x-4">
<div className="form-check">
<input className="radio" type="radio" id="male" name="isMale"
onChange={() => handleChange({ target: { name: "isMale", value: true } })}
checked={publisher.isMale}
/>
<label className="label" htmlFor="male">
Мъж
</label>
</div>
<div className="form-check">
<input className="radio" type="radio" id="female" name="isMale"
onChange={() => handleChange({ target: { name: "isMale", value: false } })}
checked={!publisher.isMale}
/>
<label className="label" htmlFor="female">
Жена
</label>
</div>
</div>
<div className="mb-4">
<label className="label" htmlFor="type">Тип</label>
<select name="type" value={publisher.type} onChange={handleChange} className="textbox" placeholder="Type" autoFocus >
<option value="Publisher">Вестител</option>
<option value="Bethelite">Бетелит</option>
<option value="RegularPioneer">Редовен Пионер</option>
<option value="SpecialPioneer">Специален Пионер/Мисионер</option>
{/* <option value="Missionary">Мисионер</option>
<option value="CircuitOverseer">Пътуваща служба</option> */}
</select>
</div>
<div className="mb-4">
<label className="label" htmlFor="town">Град</label>
<input type="text" name="town" value={publisher.town} onChange={handleChange} className="textbox" placeholder="Град" autoFocus />
</div>
<div className="mb-4">
<label className="label" htmlFor="comments">Коментари</label>
<input type="text" name="comments" value={publisher.comments} onChange={handleChange} className="textbox" placeholder="Коментари" autoFocus />
</div>
</div>
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage=" ">
<div className="mb-4">
<label className="label" htmlFor="age">Възраст</label>
<input type="number" name="age" value={publisher.age} onChange={handleChange} className="textbox" placeholder="Age" autoFocus />
</div>
<div className="mb-4">
<div className="form-check">
<input className="checkbox" type="checkbox" id="isactive" name="isactive" onChange={handleChange} checked={publisher.isactive} autoComplete="off" />
<label className="label" htmlFor="isactive">Активен</label>
<input className="checkbox" type="checkbox" id="isTrained" name="isTrained" onChange={handleChange} checked={publisher.isTrained} autoComplete="off" />
<label className="label" htmlFor="isTrained">Получил обучение</label>
<input className="checkbox disabled" type="checkbox" id="isImported" name="isImported" onChange={handleChange} checked={publisher.isImported} autoComplete="off" />
<label className="label " htmlFor="isImported">Импортиран от график</label>
</div>
</div>
<div className="mb-4">
<label className="label" htmlFor="role">Роля Потребител</label>
<select name="role" id="role" className="select" value={publisher.role} onChange={handleChange} >
{/* <option value='${UserRole.USER}'>Потребител</option> */}
<option value={`${UserRole.USER}`}>Потребител</option>
<option value={`${UserRole.EXTERNAL}`}>Външен</option>
<option value={`${UserRole.POWERUSER}`}>Организатор</option>
<option value={`${UserRole.ADMIN}`}>Администратор</option>
{/* Add other roles as needed */}
</select>
</div>
</ProtectedRoute>
{/* ---------------------------- Actions --------------------------------- */}
<div className="panel-actions">
<Link href={urls.indexUrl} className="action-button"> обратно </Link>
{/* delete */}
<button className="button bg-red-500 hover:bg-red-700 focus:outline-none focus:shadow-outline" type="button" onClick={handleDelete}>
Delete
</button>
<button className="button bg-blue-500 hover:bg-blue-700 focus:outline-none focus:shadow-outline" type="submit">
{router.query?.id ? "Update" : "Create"}
</button>
</div>
</form>
<div className="flex flex-col">
<div className="flex flex-row">
<div className="flex-col" id="shiftlist" >
<div className="">
<ShiftsList assignments={publisher.assignments} selectedtab={common.getCurrentYearMonth()} />
</div>
</div>
</div>
<div className="flex-1 p-5">
<AvailabilityList publisher={publisher} />
</div>
</div>
</div>
</div >
</>
)
};

View File

@ -0,0 +1,73 @@
import React, { useState, useEffect, useRef } from "react";
import axiosInstance from '../../src/axiosSecure';
import toast from "react-hot-toast";
import { useRouter } from "next/router";
const PublisherInlineForm = ({ publisherId, initialShiftsPerMonth }) => {
const [desiredShiftsPerMonth, setDesiredShiftsPerMonth] = useState(initialShiftsPerMonth);
const router = useRouter();
const storedValue = useRef(initialShiftsPerMonth);
useEffect(() => {
const fetchPublisherData = async () => {
if (publisherId != null) {
try {
const response = await axiosInstance.get(`/api/data/publishers/${publisherId}`);
const publisher = response.data;
setDesiredShiftsPerMonth(publisher.desiredShiftsPerMonth);
storedValue.current = publisher.desiredShiftsPerMonth;
} catch (error) {
console.error("Error fetching publisher data:", error);
toast.error("Не може да се зареди информация.");
}
}
};
//if (storedValue.current == null) {
fetchPublisherData();
//}
}, [publisherId]);
useEffect(() => {
const saveShiftsPerMonth = async () => {
if (publisherId && desiredShiftsPerMonth != null
&& initialShiftsPerMonth != desiredShiftsPerMonth
&& storedValue.current != desiredShiftsPerMonth) {
try {
await axiosInstance.put(`/api/data/publishers/${publisherId}`, {
desiredShiftsPerMonth,
});
toast.success("Смени на месец запазени", {
position: "bottom-center",
});
} catch (error) {
console.error("Error saving desired shifts per month:", error);
toast.error("Грешка при запазване на смени на месец");
}
}
};
saveShiftsPerMonth();
}, [desiredShiftsPerMonth]);
return (
<div className="flex flex-col sm:flex-row items-center space-y-2 sm:space-y-0 sm:space-x-2">
<label htmlFor="desiredShiftsPerMonth" className="block text-sm font-medium text-gray-700">
Желани смени на месец:
</label>
<input
type="number"
id="desiredShiftsPerMonth"
name="desiredShiftsPerMonth"
value={desiredShiftsPerMonth}
onChange={(e) => setDesiredShiftsPerMonth(parseInt(e.target.value))}
className="textbox mt-1 sm:mt-0 w-full sm:w-auto flex-grow"
placeholder="Желани смени на месец"
min="0" max="10"
/>
</div>
);
};
export default PublisherInlineForm;

View File

@ -0,0 +1,140 @@
import React, { useState, useEffect } from 'react';
import axiosInstance from '../../src/axiosSecure';
import common from '../../src/helpers/common';
//import { is } from 'date-fns/locale';
function PublisherSearchBox({ selectedId, onChange, isFocused, filterDate, showSearch = true, showList = false, showAllAuto = false, infoText = " Семеен глава" }) {
const [selectedItem, setSelectedItem] = useState(null);
const [searchText, setSearchText] = useState('');
const [publishers, setPublishers] = useState([]);
const [searchResults, setSearchResults] = useState([]);
const [selectedDate, setSelectedDate] = useState(filterDate);
useEffect(() => {
fetchPublishers();
}, []); // Empty dependency array ensures this useEffect runs only once
const fetchPublishers = async () => {
console.log("fetchPublishers called");
try {
let url = `/api/?action=filterPublishers&select=id,firstName,lastName,email,isactive&searchText=${searchText}&availabilities=false`;
if (filterDate) {
url += `&filterDate=${common.getISODateOnly(filterDate)}`;
}
if (showList) {
url += `&assignments=true`;
}
const { data: publishersData } = await axiosInstance.get(url);
//setPublishers(publishersData);
const activePublishers = publishersData.filter(publisher => publisher.isactive === true);
setPublishers(activePublishers);
} catch (error) {
// Handle errors
console.error("Error fetching publishers:", error);
}
};
const handleHeadSelection = (pub) => {
setSearchText('');
setSearchResults([]);
setSelectedItem(pub);
onChange(pub); // Pass the selected parent to the parent component
};
//allows us to trigger a focus on the input field when we trigger to show the search box from outside
const inputRef = React.useRef(null);
useEffect(() => {
console.log("isFocused changed = ", isFocused);
if (isFocused && inputRef.current) {
inputRef.current.focus();
}
}, [isFocused]);
// Update selectedDate filter from outside
// useEffect(() => {
// setSelectedDate(filterDate);
// console.log("filterDate changed = ", filterDate);
// }, [filterDate]);
// Update publishers when filterDate or showList changes
useEffect(() => {
fetchPublishers();
}, [filterDate, showList]);
// Update selectedItem when selectedId changes and also at the initial load
useEffect(() => {
if (publishers) {
const head = publishers.find((publisher) => publisher.id === selectedId);
if (head) {
//setSearchText(`${head.firstName} ${head.lastName}`);
setSelectedItem(head);
}
}
}, [selectedId, publishers]);
// Update searchResults when searchText or publishers change
useEffect(() => {
if (searchText || showAllAuto) {
const filteredResults = publishers.filter((publisher) => {
const fullName = `${publisher.firstName} ${publisher.lastName} `.toLowerCase();
return fullName.includes(searchText.trim().toLowerCase())
|| publisher.email.toLowerCase().includes(searchText.trim().toLowerCase());
});
setSearchResults(filteredResults);
} else {
setSearchResults([]);
}
}, [searchText, publishers]);
return (
<div className="relative">
{showSearch ? (
<>
<input ref={inputRef}
type="text"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
onFocus={() => { isFocused = true; }}
className="textbox"
placeholder={`${selectedItem?.firstName || ""} ${selectedItem?.lastName || ""}`}
/>
{(showSearch) && (searchResults.length > 0 || showAllAuto) && (
<ul className="absolute bg-white border border-gray-300 w-full z-10">
{/* showAllAuto ? publishers : searchResults */}
{(searchResults).map((publisher) => (
<li key={publisher.id}
className={`p-2 cursor-pointer hover:bg-gray-200 ${publisher.assignmentsCurrentWeek > 0 ? 'text-orange-500' : ''}`}
onClick={() => { handleHeadSelection(publisher); }} >
{publisher.firstName} {publisher.lastName}
</li>
))}
</ul>
)}
{selectedItem && infoText && (
<p className="font-semibold pl-1">
{infoText}: {selectedItem.firstName} {selectedItem.lastName}
</p>
)}
</>
) : null}
{showList ? (
// Display only clickable list of all publishers
<ul className="absolute bg-white border border-gray-300 w-full z-10">
{publishers.map((publisher) => (
<li key={publisher.id}
className="p-2 cursor-pointer hover:bg-gray-200"
onClick={() => { handleHeadSelection(publisher); }} >
{publisher.firstName} {publisher.lastName}
</li>
))}
</ul>
) : null}
</div>
);
}
export default PublisherSearchBox;

View File

@ -0,0 +1,149 @@
import React, { useState } from "react";
import toast from "react-hot-toast";
import { useRouter } from "next/router";
import Link from "next/link";
import axiosInstance from '../../src/axiosSecure';
import { monthNamesBG, GetTimeFormat, GetDateFormat } from "../../src/helpers/const"
import common from "../../src/helpers/common";
type Assignment = {
items: {
[key: string]: any[];
};
keys: string[];
months: {
[key: string]: string;
};
};
type ShiftsListProps = {
assignments: Assignment;
selectedtab: string;
};
const ShiftsList = ({ assignments, selectedtab }: ShiftsListProps) => {
const { keys: assignmentKeys = [], months = [], items = [] } = assignments || {};
const [currentTab, setCurrentTab] = useState<string>(selectedtab || assignmentKeys[-1]);
console.log("assignments = ", assignments);
console.log("currentTab = ", currentTab);
const searchReplacement = async (id) => {
try {
var assignment = (await axiosInstance.get("/api/data/ssignments/" + id)).data;
assignment.isConfirmed = true;
// assignment.isDeleted = true;
await axiosInstance.put("/api/data/assignments/" + id, assignment);
toast.success("Shift Tentative", {
position: "bottom-center",
});
// router.push(urls.indexUrl);
} catch (error) {
console.log(JSON.stringify(error));
toast.error(error.response.data.message);
}
}
const AddToGoogleCalendar = async (id) => {
try {
const { url, event } = await axiosInstance.get(`/api/shiftgenerate?action=createcalendarevent&id=${id}`, {
headers: {
'Content-Type': 'application/json',
},
});
window.open(url, '_blank')
.addEventListener('load', function () {
console.log('loaded');
});
// fetchShifts();
// console.log(shifts);
res.writeHead(301, { "Location": url });
res.end();
} catch (error) {
console.log(error);
console.log(JSON.stringify(error));
if (error.response?.data?.message) {
toast.error(error.response.data.message);
}
}
}
return (
<div className="flex flex-col m-5 w-full">
<ul className="nav nav-tabs flex flex-col md:flex-row flex-wrap list-none border-b-0 pl-0 mb-1" role="tablist">
{assignmentKeys?.slice(-4).map(m => (
<li key={m.toString()} className="nav-item">
<a
href="#"
onClick={() => setCurrentTab(m)}
className={`text-blue-500 border-l border-t border-r inline-block py-2 px-4 ${currentTab === m ? 'active border-gray-300 font-bold' : ' border-transparent'}`}
// className={`nav-link block font-medium text-xs leading-tight uppercase border-x-0 border-t-0 border-b-2 ${currentTab === m ? "active border-blue-500 font-bold" : "border-transparent"} px-6 py-3 my-2 hover:border-transparent hover:bg-gray-100 focus:border-transparent`}
role="tab"
aria-controls={"tabs-" + m}
aria-selected={currentTab === m}
>
{months[m]}
</a>
</li>
))}
</ul>
<div className="tab-content flex flow w-full p-2 border-2 border-gray-300 rounded-md">
{assignmentKeys?.map(month => (
// <div className={`tab-pane fade ${month === currentTab ? "active show" : ""}`} key={month.toString()} role="tabpanel" aria-labelledby={"tabs-tab" + month}>
//if tab is selected
//common.getCurrentYearMonth(month)
currentTab === month ?
<div key={month} className={`tab-pane fade ${month === currentTab ? "active show" : ""}`} role="tabpanel" aria-labelledby={"tabs-tab" + month}>
<div className="flex items-center py-3 px-4">
<span className="text-xl font-medium">
{items[month]?.filter(Boolean).reduce((total, item) => (
Array.isArray(item) || typeof item === 'object' ? total + Object.keys(item).length : total
), 0)} смени за {months[month]}
</span>
</div>
{items[month]?.map((shiftDay, i) => (
shiftDay && shiftDay.length > 0 ? (
<div className="flex items-center space-x-2 py-1" key={i}>
<div className="font-bold flex-shrink-0 w-6 text-right">{i + ":"}</div> {/*This is the column for the date. I've given it a fixed width (w-8) which you can adjust*/}
<div className="flex-grow flex">
{shiftDay.map(assignment => (
<div className="flow space-x-2 bg-gray-200 rounded-lg shadow-md py-2 px-3" key={assignment.id}>
<span>{GetTimeFormat(assignment.start)} - {GetTimeFormat(assignment.end)}</span>
<button
className={`text-sm text-white font-semibold px-2 rounded-lg shadow ${assignment.isConfirmed ? "bg-yellow-400 hover:bg-yellow-500" : "bg-red-400 hover:bg-red-500"}`}
onClick={() => searchReplacement(assignment.id)}
>
Търси заместник
</button>
<button
className="text-sm bg-green-400 hover:bg-green-500 text-white font-semibold px-2 rounded-lg shadow"
onClick={() => AddToGoogleCalendar(assignment.id)}
>
Добави в календар
</button>
</div>
))}
</div>
</div>) : null
))}
</div>
: null
))}
</div>
</div >
);
}
export default ShiftsList;