initial commit - code moved to separate repo
This commit is contained in:
87
pages/cart/publishers/contacts.tsx
Normal file
87
pages/cart/publishers/contacts.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import { useState } from 'react';
|
||||
import Layout from "../../../components/layout";
|
||||
import ProtectedRoute from '../../../components/protectedRoute';
|
||||
import { UserRole } from '@prisma/client';
|
||||
import axiosServer from '../../../src/axiosServer';
|
||||
import common from '../../../src/helpers/common';
|
||||
|
||||
function ContactsPage({ publishers }) {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
const filteredPublishers = publishers.filter((publisher) =>
|
||||
publisher.firstName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
publisher.lastName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
publisher.email.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
publisher.phone?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<ProtectedRoute allowedRoles={[UserRole.ADMIN, UserRole.POWERUSER, UserRole.USER]}>
|
||||
<div className="container mx-auto p-4">
|
||||
<h1 className="text-xl font-semibold mb-4">Контакти</h1>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Търси по име, имейл или телефон..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="border border-gray-300 rounded-md px-2 py-2 mb-4 w-full text-base md:text-sm"
|
||||
/>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="border-b font-medium p-4 pl-8 pt-0 pb-3">Име</th>
|
||||
<th className="border-b font-medium p-4 pt-0 pb-3">Имейл</th>
|
||||
<th className="border-b font-medium p-4 pt-0 pb-3">Телефон</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredPublishers.map((publisher) => (
|
||||
<tr key={publisher.id}>
|
||||
<td className="border-b p-4 pl-8">{publisher.firstName} {publisher.lastName}</td>
|
||||
<td className="border-b p-4">
|
||||
<a href={`mailto:${publisher.email}`} className="text-blue-500">{publisher.email}</a>
|
||||
</td>
|
||||
<td className="border-b p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className={common.isValidPhoneNumber(publisher.phone) ? '' : 'text-red-500'}>{publisher.phone}</span>
|
||||
<div className="flex items-center">
|
||||
<a href={`tel:${publisher.phone}`} className="inline-block p-2 mr-2">
|
||||
<i className="fas fa-phone-alt text-blue-500 text-xl" title="Обаждане"></i>
|
||||
</a>
|
||||
<a href={`https://wa.me/${publisher.phone}`} className="inline-block p-2 mr-2">
|
||||
<i className="fab fa-whatsapp text-green-500 text-xl" title="WhatsApp"></i>
|
||||
</a>
|
||||
{publisher.phone ? (
|
||||
<a href={`viber://chat/?number=%2B${publisher.phone.startsWith('+') ? publisher.phone.substring(1) : publisher.phone}`} className="inline-block p-2">
|
||||
<i className="fab fa-viber text-purple-500 text-xl" title="Viber"></i>
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</ProtectedRoute>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default ContactsPage;
|
||||
|
||||
export const getServerSideProps = async (context) => {
|
||||
const axios = await axiosServer(context);
|
||||
const { data: publishers } = await axios.get('/api/data/publishers?select=id,firstName,lastName,email,phone');
|
||||
|
||||
return {
|
||||
props: {
|
||||
publishers,
|
||||
},
|
||||
};
|
||||
};
|
105
pages/cart/publishers/edit/[id].tsx
Normal file
105
pages/cart/publishers/edit/[id].tsx
Normal file
@ -0,0 +1,105 @@
|
||||
import axiosServer from '../../../../src/axiosServer';
|
||||
import NewPubPage from "../new";
|
||||
export default NewPubPage;
|
||||
|
||||
import { Assignment, Shift, UserRole } from "prisma/prisma-client";
|
||||
// import { monthNamesBG } from "~/src/helpers/const"
|
||||
import { monthNamesBG } from "src/helpers/const";
|
||||
|
||||
function getShiftGroups(shifts: [Shift]) {
|
||||
|
||||
const groupedShifts = shifts.reduce((groups, shift) => {
|
||||
// Extract the year and month from the shift date
|
||||
const yearMonth = shift.startTime.substring(0, 7)
|
||||
// Initialize the group for the year-month if it doesn't exist
|
||||
if (!groups[yearMonth]) {
|
||||
groups[yearMonth] = []
|
||||
}
|
||||
// Add the shift to the group
|
||||
groups[yearMonth].push(shift)
|
||||
// Return the updated groups object
|
||||
return groups
|
||||
}, {})
|
||||
|
||||
// Sort the groups by year-month
|
||||
const sortedGroups = Object.keys(groupedShifts).sort((a, b) => {
|
||||
// Compare the year-month strings lexicographically
|
||||
if (a < b) return -1
|
||||
if (a > b) return 1
|
||||
return 0
|
||||
}).reduce((result, key) => {
|
||||
// Rebuild the object with the sorted keys
|
||||
result[key] = groupedShifts[key]
|
||||
return result
|
||||
}, {})
|
||||
return sortedGroups;
|
||||
}
|
||||
|
||||
export const getServerSideProps = async (context) => {
|
||||
const axios = await axiosServer(context);
|
||||
|
||||
context.res.setHeader("Cache-Control", "s-maxage=1, stale-while-revalidate");
|
||||
if (!context.query || !context.query.id) {
|
||||
return {
|
||||
props: {}
|
||||
};
|
||||
}
|
||||
var url = process.env.NEXTAUTH_URL + "/api/data/publishers/" + context.query.id + "?include=availabilities,assignments,assignments.shift";
|
||||
console.log("GET PUBLISHER FROM:" + url)
|
||||
const { data: item } = await axios.get(url);
|
||||
|
||||
// item.allShifts = item.assignments.map((a: Assignment[]) => a.shift);
|
||||
|
||||
//group shifts by month, remove duplicates
|
||||
//sort availabilities by start time
|
||||
// item.availabilities = item.availabilities
|
||||
// .sort((a, b) => b.startTime - a.startTime);
|
||||
|
||||
item.assignments = item.assignments
|
||||
.sort((a, b) => b.startTime - a.startTime)
|
||||
.reduce((acc, assignment: Assignment) => {
|
||||
|
||||
const date = new Date(assignment.shift.startTime);
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth();
|
||||
const tabId = year + "-" + month;
|
||||
const tabName = monthNamesBG[month] + " " + year;
|
||||
const day = date.getDate();
|
||||
// console.log("shift: year: " + year + " month: " + month + " day: " + day);
|
||||
if (!acc.items[tabId]) {
|
||||
acc.items[tabId] = [];
|
||||
}
|
||||
if (!acc.items[tabId][day]) {
|
||||
acc.items[tabId][day] = [];
|
||||
}
|
||||
//remove duplicates
|
||||
if (acc.items[tabId][day].find(s => s.id == assignment.shift.id)) {
|
||||
return acc;
|
||||
}
|
||||
// acc.months = acc.months || [];
|
||||
if (!acc.months[tabId]) {
|
||||
acc.months[tabId] = [];
|
||||
}
|
||||
|
||||
if (!acc.keys.includes(tabId)) {
|
||||
acc.months[tabId] = tabName;
|
||||
acc.keys.push(tabId);
|
||||
}
|
||||
|
||||
acc.items[tabId][day].push({
|
||||
start: assignment.shift.startTime,
|
||||
end: assignment.shift.endTime,
|
||||
id: assignment.id,
|
||||
shiftId: assignment.shift.id,
|
||||
isConfirmed: assignment.isConfirmed ? true : false,
|
||||
});
|
||||
return acc;
|
||||
}, { items: {}, keys: [], months: {} });
|
||||
// console.log("server item:");
|
||||
// console.dir(item, { depth: null });
|
||||
return {
|
||||
props: {
|
||||
item: item
|
||||
},
|
||||
};
|
||||
};
|
20
pages/cart/publishers/edit/me.tsx
Normal file
20
pages/cart/publishers/edit/me.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
// pages/me.jsx
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useSession } from 'next-auth/react';
|
||||
|
||||
export default function Me() {
|
||||
const router = useRouter();
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'authenticated') {
|
||||
router.push(`/cart/publishers/edit/${session.user.id}?self=true`);
|
||||
} else if (status === 'unauthenticated') {
|
||||
router.push('/api/auth/signin');
|
||||
}
|
||||
}, [status, session, router]);
|
||||
|
||||
// You can add a fallback content or loader here if you want
|
||||
return <div>Redirecting...</div>;
|
||||
}
|
629
pages/cart/publishers/import.tsx
Normal file
629
pages/cart/publishers/import.tsx
Normal file
@ -0,0 +1,629 @@
|
||||
import { toast } from 'react-toastify';
|
||||
import Layout from "../../../components/layout";
|
||||
import { Publisher, Availability, AvailabilityType, DayOfWeek, UserRole } from "@prisma/client";
|
||||
import ProtectedRoute from '../../../components/protectedRoute';
|
||||
|
||||
import axiosInstance from '../../../src/axiosSecure';
|
||||
import { useState, useRef } from "react";
|
||||
|
||||
|
||||
import * as XLSX from "xlsx";
|
||||
// import { Table } from "react-bootstrap";
|
||||
import { staticGenerationAsyncStorage } from "next/dist/client/components/static-generation-async-storage.external";
|
||||
|
||||
import moment from 'moment';
|
||||
// import { DatePicker } from '@mui/x-date-pickers'; !! CAUSERS ERROR ???
|
||||
|
||||
// import { DatePicker } from '@mui/x-date-pickers/DatePicker';
|
||||
import { set } from 'date-fns';
|
||||
|
||||
// import * as common from "src/helpers/common";
|
||||
const common = require('../../../src/helpers/common');
|
||||
|
||||
export default function ImportPage() {
|
||||
const [data, setData] = useState([])
|
||||
const [rawData, setRawData] = useState([]);
|
||||
const [status, setStatus] = useState({ status: 'idle', info: '' });
|
||||
|
||||
const MODE_PUBLISHERS1 = "PUBLISHERS1";
|
||||
const MODE_PUBLISHERS2 = "PUBLISHERS2";
|
||||
type ModeState = {
|
||||
mainMode: typeof MODE_PUBLISHERS1 | typeof MODE_PUBLISHERS2;
|
||||
schedule: boolean;
|
||||
publishers2Import: boolean;
|
||||
headerRow: number;
|
||||
}
|
||||
|
||||
const [mode, setMode] = useState<ModeState>({
|
||||
mainMode: MODE_PUBLISHERS1,
|
||||
schedule: false,
|
||||
publishers2Import: false,
|
||||
headerRow: 0
|
||||
});
|
||||
|
||||
const headerRef = useRef({
|
||||
header: null,
|
||||
dateIndex: -1,
|
||||
emailIndex: -1,
|
||||
nameIndex: -1,
|
||||
phoneIndex: -1,
|
||||
isTrainedIndex: -1,
|
||||
desiredShiftsIndex: -1,
|
||||
dataStartIndex: -1,
|
||||
isActiveIndex: -1,
|
||||
pubTypeIndex: -1
|
||||
});
|
||||
|
||||
const handleFile = (e) => {
|
||||
const [file] = e.target.files;
|
||||
const reader = new FileReader();
|
||||
reader.onload = (evt) => {
|
||||
const bstr = evt.target.result;
|
||||
const wb = XLSX.read(bstr, { type: "binary" });
|
||||
const wsname = wb.SheetNames[0];
|
||||
const ws = wb.Sheets[wsname];
|
||||
const sheetData = XLSX.utils.sheet_to_json(ws, {
|
||||
header: 1,
|
||||
range: 0,
|
||||
blankrows: false,
|
||||
defval: '',
|
||||
});
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (sheetData[i].includes('Имейл')) {
|
||||
setMode({
|
||||
mainMode: MODE_PUBLISHERS1,
|
||||
schedule: false,
|
||||
publishers2Import: false,
|
||||
headerRow: i
|
||||
});
|
||||
|
||||
headerRef.current.header = sheetData[i];
|
||||
headerRef.current.dataStartIndex = i;
|
||||
common.logger.debug("header at row " + i);
|
||||
break;
|
||||
}
|
||||
// it seems we are processing availability sheet. import only publishers by default, and read only the first 3 columns as publisher data
|
||||
if (sheetData[i].includes('Email Address')) {
|
||||
setMode({
|
||||
mainMode: MODE_PUBLISHERS2,
|
||||
schedule: true,
|
||||
publishers2Import: false,
|
||||
headerRow: i
|
||||
});
|
||||
headerRef.current.header = sheetData[i];
|
||||
headerRef.current.dataStartIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!headerRef.current.header) {
|
||||
console.error("header not found in the first 5 rows!");
|
||||
return;
|
||||
}
|
||||
|
||||
const header = headerRef.current.header
|
||||
headerRef.current.dateIndex = header.indexOf('Timestamp');
|
||||
headerRef.current.emailIndex = header.indexOf('Имейл') !== -1 ? header.indexOf('Имейл') : header.indexOf('Email Address');
|
||||
headerRef.current.nameIndex = header.indexOf('Име, Фамилия');
|
||||
headerRef.current.phoneIndex = header.indexOf('Телефон');
|
||||
headerRef.current.isTrainedIndex = header.indexOf('Обучен');
|
||||
headerRef.current.desiredShiftsIndex = header.indexOf('Желан брой участия');
|
||||
headerRef.current.isActiveIndex = header.indexOf("Неактивен");
|
||||
headerRef.current.pubTypeIndex = header.indexOf("Назначение");
|
||||
|
||||
const filteredData = sheetData.slice(headerRef.current.dataStartIndex).map((row) => {
|
||||
let date;
|
||||
date = common.excelSerialDateToDate(row[headerRef.current.dateIndex]);
|
||||
//substract 1 day, because excel serial date is 1 day ahead
|
||||
date.setDate(date.getDate() - 1);
|
||||
date = common.getDateFormated(date);
|
||||
common.logger.debug(date);
|
||||
return [date, row[headerRef.current.emailIndex], row[headerRef.current.nameIndex]];
|
||||
});
|
||||
|
||||
setRawData(sheetData);
|
||||
setData(filteredData);
|
||||
setStatus({ status: 'зареден', info: `Заредени ${filteredData.length} от ${rawData.length} записа` });
|
||||
|
||||
};
|
||||
reader.readAsBinaryString(file);
|
||||
|
||||
// Reset the file input value
|
||||
e.target.value = null;
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
common.logger.debug("handleSave to: " + common.getBaseUrl());
|
||||
const header = rawData[mode.headerRow];
|
||||
for (let i = mode.headerRow + 1; i < rawData.length; i++) { //fullData.length; each publisher
|
||||
//update status.info with current publisher
|
||||
setStatus({ status: 'running', info: `Processing row ${i} of ${rawData.length}` });
|
||||
//sleep for 300ms to allow the database to process the previous request
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
|
||||
const row = rawData[i];
|
||||
var email, phone, names, dateOfInput, oldAvDeleted = false, isTrained = false, desiredShiftsPerMonth = 4, isActive = false;
|
||||
//const date = new Date(row[0]).toISOS{tring().slice(0, 10);
|
||||
if (mode.mainMode == MODE_PUBLISHERS1) {
|
||||
|
||||
email = row[headerRef.current.emailIndex];
|
||||
|
||||
phone = row[headerRef.current.phoneIndex].toString().trim(); // Trim whitespace
|
||||
// Remove any non-digit characters, except for the leading +
|
||||
//phone = phone.replace(/(?!^\+)\D/g, '');
|
||||
phone = phone.replace(/[^+\d]/g, '');
|
||||
if (phone.startsWith('8') || phone.startsWith('9')) {
|
||||
phone = '+359' + phone.substring(1); // Assumes all numbers starting with 8 are Bulgarian and should have +359
|
||||
} else if (!phone.startsWith('+')) {
|
||||
phone = '+' + phone; // Add + if it's missing, assuming the number is complete
|
||||
}
|
||||
names = row[headerRef.current.nameIndex].normalize('NFC').split(/[ ]+/);
|
||||
dateOfInput = importDate.value || new Date().toISOString();
|
||||
// not empty == true
|
||||
isTrained = row[headerRef.current.isTrainedIndex] !== '';
|
||||
isActive = row[headerRef.current.isActiveIndex] == '';
|
||||
desiredShiftsPerMonth = row[headerRef.current.desiredShiftsIndex] !== '' ? row[headerRef.current.desiredShiftsIndex] : 4;
|
||||
}
|
||||
else {
|
||||
dateOfInput = common.excelSerialDateToDate(row[0]);
|
||||
email = row[1];
|
||||
names = row[2].normalize('NFC').split(/[, ]+/);
|
||||
}
|
||||
|
||||
//remove special characters from name
|
||||
|
||||
// let names = common.removeAccentsAndSpecialCharacters(row[2]).split(/[, ]+/);
|
||||
|
||||
let personId = '';
|
||||
try {
|
||||
try {
|
||||
const select = "&select=id,firstName,lastName,phone,isTrained,desiredShiftsPerMonth,isactive,availabilities";
|
||||
const responseByName = await axiosInstance.get(`/api/?action=findPublisher&filter=${names.join(' ')}${select}`);
|
||||
let existingPublisher = responseByName.data[0];
|
||||
if (!existingPublisher) {
|
||||
// If no match by name, check by email
|
||||
const responseByEmail = await axiosInstance.get(`/api/?action=findPublisher&email=${email}${select}`);
|
||||
if (responseByEmail.data.length > 0) {
|
||||
// Iterate over all matches by email to find one with a matching or similar name
|
||||
const fullName = names.join(' ').toLowerCase(); // Simplify comparison
|
||||
existingPublisher = responseByEmail.data.find(publisher => {
|
||||
const publisherFullName = (publisher.firstName + ' ' + publisher.lastName).toLowerCase();
|
||||
return fullName === publisherFullName; // Consider expanding this comparison for typos
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (existingPublisher?.id) { // UPDATE
|
||||
// Create a flag to check if update is needed
|
||||
const updatedData = {};
|
||||
personId = existingPublisher?.id;
|
||||
let updateNeeded = false;
|
||||
|
||||
// Check for name update
|
||||
const fullName = names.join(' ');
|
||||
const existingFullName = existingPublisher.firstName + ' ' + existingPublisher.lastName;
|
||||
if (fullName !== existingFullName) {
|
||||
common.logger.debug(`Existing publisher '${existingFullName}' found for ${email} (ID:${personId})`);
|
||||
updatedData.firstName = names[0];
|
||||
updatedData.lastName = names.slice(1).join(' ');
|
||||
data[i - mode.headerRow][4] = "name updated!";
|
||||
updateNeeded = true;
|
||||
} else {
|
||||
data[i - mode.headerRow][4] = "existing";
|
||||
}
|
||||
|
||||
// Log existing publisher
|
||||
common.logger.debug(`Existing publisher '${[existingPublisher.firstName, existingPublisher.lastName].join(' ')}' found for ${email} (ID:${personId})`);
|
||||
|
||||
|
||||
// Check for other updates
|
||||
const fieldsToUpdate = [
|
||||
{ key: 'phone', value: phone },
|
||||
{ key: 'desiredShiftsPerMonth', value: desiredShiftsPerMonth, parse: parseInt },
|
||||
{ key: 'isTrained', value: isTrained },
|
||||
{ key: 'isactive', value: isActive }
|
||||
];
|
||||
|
||||
fieldsToUpdate.forEach(({ key, value, parse }) => {
|
||||
if (!existingPublisher[key] && value !== '' && value !== undefined) {
|
||||
updatedData[key] = parse ? parse(value) : value;
|
||||
updateNeeded = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Update the record if needed and if MODE_PUBLISHERS1 (Import from List of Participants)
|
||||
if (updateNeeded && (mode.publishers2Import || mode.mainMode == MODE_PUBLISHERS1)) {
|
||||
try {
|
||||
await axiosInstance.put(`/api/data/publishers/${personId}`, updatedData);
|
||||
common.logger.debug(`Updated publisher ${personId}`);
|
||||
data[i - mode.headerRow][4] = "updated";
|
||||
} catch (error) {
|
||||
console.error(`Failed to update publisher ${personId}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
} else { // CREATE
|
||||
|
||||
// If no publisher with the email exists, create one
|
||||
// const names = email.split('@')[0].split('.');\
|
||||
|
||||
//Save new publisher
|
||||
if (mode.publishers2Import) {
|
||||
const personResponse = await axiosInstance.post('/api/data/publishers', {
|
||||
email,
|
||||
phone,
|
||||
firstName: names[0],
|
||||
lastName: names[1],
|
||||
isactive: isActive,
|
||||
isTrained,
|
||||
desiredShiftsPerMonth,
|
||||
});
|
||||
personId = personResponse.data.id;
|
||||
data[i][4] = "new";
|
||||
}
|
||||
else
|
||||
if (mode.mainMode == MODE_PUBLISHERS1) {
|
||||
const firstname = names.length > 2 ? names.slice(0, -1).join(' ') : names[0];
|
||||
const personResponse = await axiosInstance.post('/api/data/publishers', {
|
||||
email,
|
||||
phone,
|
||||
firstName: firstname,
|
||||
lastName: names[names.length - 1],
|
||||
isactive: isActive,
|
||||
isTrained,
|
||||
desiredShiftsPerMonth
|
||||
});
|
||||
data[i - mode.headerRow][4] = "new";
|
||||
} else {
|
||||
data[i - mode.headerRow][4] = "import disabled";
|
||||
}
|
||||
|
||||
common.logger.debug(`NEW Publisher ${personId} created for email ${email} (${names})`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
data[i - mode.headerRow][4] = "error";
|
||||
}
|
||||
|
||||
if (mode.schedule) {
|
||||
// Parse the availability data from the Excel cell
|
||||
//get days of the month and add up to the next full week
|
||||
// Save availability records
|
||||
const availabilities: Availability[] = [];
|
||||
|
||||
for (let j = 3; j < header.length; j++) {
|
||||
const dayHeader = header[j];
|
||||
|
||||
const shifts = row[j];
|
||||
if (!shifts || shifts === 'Не мога') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// specific date: Седмица (17-23 април) [Четвъртък ]
|
||||
// const regex = /^Седмица \((\d{1,2})-(\d{1,2}) (\S+)\) \[(\S+)\]$/;
|
||||
// specific week: Седмица 1 <any character> [Четвъртък]
|
||||
// const regex = /^Седмица (\d{1,2}) \((\d{1,2})-(\d{1,2}) (\S+)\) \[(\S+)\]$/;
|
||||
//allow optional space before and after the brackets
|
||||
// match both 'Седмица 3 (20-25 ноември) [пон]' and 'Седмица 4 (27 ноември - 2 декември) [четв]'
|
||||
//const regex = /^\s*Седмица\s+(\d{1,2})\s+\((\d{1,2})\s+(\S+)(?:\s*-\s*(\d{1,2})\s+(\S+))?\)\s*\[\s*(\S+)\s*\]\s*$/;
|
||||
//the original, but missing the two month names
|
||||
let regex = /^Седмица (\d{1,2}) \((\d{1,2})-(\d{1,2}) (\S+)\)\s*\[(\S+)\s*\]\s*$/;
|
||||
regex = /^Седмица (\d{1,2}) \((\d{1,2})(?:\s*-\s*(\d{1,2}))? (\S+)(?:\s*-\s*(\d{1,2}) (\S+))?\)\s*\[(\S+)\s*\]\s*$/;
|
||||
//const regex = /^Седмица (\d{1,2}) \((\d{1,2}(\s*\S*?))-(\d{1,2}) (\S+)\)\s*\[(\S+)\s*\]\s*$/;
|
||||
|
||||
//both Седмица 1 (6-11 ноември) [пон]
|
||||
// Седмица 4 (27 ноември-2 декември) [пет]
|
||||
//const regex = /^Седмица (\d{1,2}) \((\d{1,2} \S+)?-? ?(\d{1,2} \S+)\)\s*\[(\S+)\s*\]\s*$/;
|
||||
|
||||
//const regex = /^Седмица (\d{1,2}) \((\d{1,2} \S+)?-? ?(\d{1,2} \S+)\)\s*\[(\S+)\s*\]\s*$/;
|
||||
|
||||
|
||||
// const regex = /^Седмица (\d{1,2}) \* \[(\S+)\]$/;
|
||||
// const regex = /^Седмица (\d{1,2}) \[(\S+)\]$/;
|
||||
|
||||
// replace multiple spaces with single space
|
||||
const normalizedHeader = dayHeader.replace(/\s+/g, ' ');
|
||||
var match = normalizedHeader.match(regex);
|
||||
|
||||
if (!match) {
|
||||
common.logger.debug("was not able to parse availability " + shifts + "trying again with different regex");
|
||||
let regex = /^Седмица (\d{1,2}) \((\d{1,2})-(\d{1,2}) (\S+)\)\s*\[(\S+)\s*\]\s*$/;
|
||||
match = normalizedHeader.match(regex);
|
||||
}
|
||||
if (match) {
|
||||
//ToDo: can't we use date.getDayEuropean() instead of this logic down?
|
||||
const weekNr = parseInt(match[1]);
|
||||
const weekStart = match[2];
|
||||
// const endDate = match[2];
|
||||
const month = match[4];
|
||||
const dayOfWeekStr = match[7];
|
||||
const dayOfWeek = common.getDayOfWeekIndex(dayOfWeekStr);
|
||||
common.logger.debug("processing availability for week " + weekNr + ": " + weekStart + "." + month + "." + dayOfWeekStr)
|
||||
// Create a new Date object for the start date of the range
|
||||
const day = new Date();
|
||||
day.setDate(1); // Set to the first day of the month to avoid overflow
|
||||
//day.setMonth(day.getMonth() + 1); // Add one month to the date, because we assume we are p
|
||||
day.setMonth(common.getMonthIndex(month));
|
||||
day.setDate(parseInt(weekStart) + dayOfWeek);
|
||||
day.setHours(0, 0, 0, 0);
|
||||
|
||||
common.logger.debug("processing availabilities for " + day.toLocaleDateString()); // Output: Sun Apr 17 2022 14:07:11 GMT+0300 (Eastern European Summer Time)
|
||||
common.logger.debug("parsing availability input: " + shifts); // Output: 0 (Sunday)
|
||||
const dayOfWeekName = common.getDayOfWeekNameEnEnum(day);
|
||||
|
||||
let dayOfMonth = day.getDate();
|
||||
const name = `${names[0]} ${names[1]}`;
|
||||
const intervals = shifts.split(",");
|
||||
|
||||
if (!oldAvDeleted && personId) {
|
||||
if (mode.schedule && email) {
|
||||
common.logger.debug(`Deleting existing availabilities for publisher ${personId} for date ${day}`);
|
||||
try {
|
||||
await axiosInstance.post(`/api/?action=deleteAvailabilityForPublisher&publisherId=${personId}&date=${day}&deleteFromPreviousAssignments=true`);
|
||||
common.logger.debug(`Deleted all availabilities for publisher ${personId}`);
|
||||
oldAvDeleted = true;
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`Failed to delete availabilities for publisher ${personId}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
let parsedIntervals: { end: number; }[] = [];
|
||||
intervals.forEach(interval => {
|
||||
// Summer format: (12:00-14:00)
|
||||
//if (!/\(\d{1,2}:\d{2}-\d{1,2}:\d{2}\)/.test(interval)) {
|
||||
//winter regex:
|
||||
//\d{1,2}-\d{1,2}:\d{2}/
|
||||
// Regular expression to match both Summer format (12:00-14:00) and winter format '09-10:30'
|
||||
const regex = /\d{1,2}(?::\d{2})?-\d{1,2}(?::\d{2})?/;
|
||||
if (!regex.test(interval)) {
|
||||
common.logger.debug(`Skipping invalid interval: ${interval}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the interval matches the format, remove any non-essential characters
|
||||
const cleanedInterval = interval.replace(/[()]/g, "");
|
||||
|
||||
// Extract start and end times from interval string
|
||||
const [start, end] = cleanedInterval.split("-");
|
||||
|
||||
// Convert times like "12" to "12:00" for consistent parsing
|
||||
const formattedStart = start.includes(":") ? start : start + ":00";
|
||||
const formattedEnd = end.includes(":") ? end : end + ":00";
|
||||
|
||||
// Try to parse the times, and skip the interval if it can't be parsed
|
||||
try {
|
||||
const parsedStart = Number(formattedStart.split(":").join(""));
|
||||
const parsedEnd = Number(formattedEnd.split(":").join(""));
|
||||
// Store parsed interval
|
||||
parsedIntervals.push({
|
||||
start: parsedStart,
|
||||
end: parsedEnd
|
||||
});
|
||||
} catch (error) {
|
||||
common.logger.debug(`Error parsing interval: ${interval}`);
|
||||
return;
|
||||
}
|
||||
|
||||
});
|
||||
// Sort intervals by start time
|
||||
parsedIntervals.sort((a, b) => a.start - b.start);
|
||||
|
||||
// Initialize start and end times with the first interval
|
||||
let minStartTime = parsedIntervals[0].start;
|
||||
let maxEndTime = parsedIntervals[0].end;
|
||||
|
||||
let isOld = false;
|
||||
// Calculate the total month difference by considering the year difference
|
||||
let totalMonthDifference = (day.getFullYear() - dateOfInput.getFullYear()) * 12 + (day.getMonth() - dateOfInput.getMonth());
|
||||
// If the total month difference is 2 or more, set isOld to true
|
||||
if (totalMonthDifference >= 2) {
|
||||
isOld = true;
|
||||
}
|
||||
|
||||
// Iterate over intervals
|
||||
for (let i = 1; i < parsedIntervals.length; i++) {
|
||||
if (parsedIntervals[i].start > maxEndTime) {
|
||||
availabilities.push(createAvailabilityObject(minStartTime, maxEndTime, day, dayOfWeekName, dayOfMonth, weekNr, personId, name, isOld));
|
||||
minStartTime = parsedIntervals[i].start;
|
||||
maxEndTime = parsedIntervals[i].end;
|
||||
} else {
|
||||
maxEndTime = Math.max(maxEndTime, parsedIntervals[i].end);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last availability
|
||||
availabilities.push(createAvailabilityObject(minStartTime, maxEndTime, day, dayOfWeekName, dayOfMonth, weekNr, personId, name, isOld));
|
||||
|
||||
}
|
||||
else {
|
||||
common.logger.debug("availability not matched. header:" + dayHeader + " shifts:" + shifts);
|
||||
}
|
||||
}
|
||||
|
||||
common.logger.debug("availabilities to save for " + personId + ": " + availabilities.length);
|
||||
// Send a single request to create all availabilities
|
||||
axiosInstance.post('/api/?action=createAvailabilities', availabilities)
|
||||
.then(response => common.logger.debug('Availabilities created:', response.data))
|
||||
.catch(error => console.error('Error creating availabilities:', error));
|
||||
|
||||
// Experimental: add availabilities to all publishers with the same email
|
||||
//check if more than one publisher has the same email, and add the availabilities to both
|
||||
//check existing publishers with the same email
|
||||
var sameNamePubs = axiosInstance.get(`/api/?action=findPublisher&all=true&email=${email}&select=id,firstName,lastName`);
|
||||
sameNamePubs.then(function (response) {
|
||||
common.logger.debug("same name pubs: " + response.data.length);
|
||||
if (response.data.length > 1) {
|
||||
response.data.forEach(pub => {
|
||||
//check the publisher is not the same as the one we already added the availabilities to
|
||||
if (pub.id != personId) {
|
||||
//change the publisher id to the new one
|
||||
availabilities.forEach(availability => {
|
||||
availability.publisherId = pub.id;
|
||||
}
|
||||
);
|
||||
//delete existing availabilities for the publisher
|
||||
axiosInstance.post(`/api/?action=deleteAvailabilityForPublisher&publisherId=${pub.id}&date=${dateOfInput}`);
|
||||
// Send a single request to create all availabilities
|
||||
axiosInstance.post('/api/?action=createAvailabilities', availabilities)
|
||||
.then(response => common.logger.debug('Availabilities created:', response.data))
|
||||
.catch(error => console.error('Error creating availabilities:', error));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// await axios.post("/api/data/availabilities", availabilities);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
toast.info('Records saved successfully', { autoClose: 30000 });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error('An error occurred while saving records!');
|
||||
}
|
||||
};
|
||||
|
||||
// Function to create an availability object
|
||||
function createAvailabilityObject(start: any, end: number, day: Date, dayOfWeekName: any, dayOfMonth: number, weekNr: number, personId: string, name: string, isFromPreviousMonth: boolean): Availability {
|
||||
const formatTime = (time) => {
|
||||
const paddedTime = String(time).padStart(4, '0');
|
||||
return new Date(day.getFullYear(), day.getMonth(), day.getDate(), parseInt(paddedTime.substr(0, 2)), parseInt(paddedTime.substr(2, 4)));
|
||||
};
|
||||
|
||||
const startTime = formatTime(start);
|
||||
const endTime = formatTime(end);
|
||||
|
||||
return {
|
||||
id: 0, // Add the missing 'id' property
|
||||
publisherId: personId,
|
||||
name,
|
||||
dayofweek: dayOfWeekName,
|
||||
dayOfMonth,
|
||||
weekOfMonth: weekNr, // Add the missing 'weekOfMonth' property
|
||||
startTime,
|
||||
endTime,
|
||||
isactive: true,
|
||||
type: AvailabilityType.OneTime,
|
||||
isWithTransportIn: false, // Add the missing 'isWithTransport' property
|
||||
isWithTransportOut: false, // Add the missing 'isWithTransport' property
|
||||
isFromPreviousAssignment: false, // Add the missing 'isFromPreviousAssignment' property
|
||||
isFromPreviousMonth: isFromPreviousMonth // Add the missing 'isFromPreviousMonth' property
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<ProtectedRoute allowedRoles={[UserRole.POWERUSER, UserRole.ADMIN]}>
|
||||
|
||||
<div className="p-6">
|
||||
<h1 className="text-3xl mb-4 font-semibold">Import Page</h1>
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700" htmlFor="fileInput">Choose a file to import:</label>
|
||||
<input type="file" id="fileInput" onChange={handleFile} className="mt-1 p-2 border rounded" placeholder="Choose a file" />
|
||||
</div>
|
||||
|
||||
{/* <DatePicker label="Дата" value={date} onChange={setDate} /> */}
|
||||
|
||||
<div className="mb-4 space-y-2"> {/* Adjust this value as needed */}
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
value="PUBLISHERS1"
|
||||
checked={mode.mainMode === "PUBLISHERS1"}
|
||||
onChange={() => setMode({ mainMode: "PUBLISHERS1", schedule: false, publishers2Import: false })}
|
||||
className="text-indigo-600"
|
||||
/>
|
||||
<span className="ml-2 space-x-4">Импортирай от Списък с участниците</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
value="PUBLISHERS2"
|
||||
checked={mode.mainMode === "PUBLISHERS2"}
|
||||
onChange={() => setMode(prev => ({ ...prev, mainMode: "PUBLISHERS2" }))}
|
||||
className="text-indigo-600"
|
||||
/>
|
||||
<span className="ml-2">Импортирай от Предпочитания за колички</span>
|
||||
</label>
|
||||
<label className="flex items-center">
|
||||
|
||||
{/* <DatePicker
|
||||
label="Дата на импорт"
|
||||
value={new Date()}
|
||||
onChange={(date) => {
|
||||
common.logger.debug("date changed to " + date);
|
||||
}}
|
||||
/> */}
|
||||
|
||||
{/* simple HTML datepicker for import date */}
|
||||
<input type="date" id="importDate" name="importDate" />
|
||||
</label>
|
||||
|
||||
{mode.mainMode === "PUBLISHERS2" && (
|
||||
<div className="mt-2 space-y-1 pl-6">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={mode.schedule}
|
||||
onChange={(e) => setMode(prev => ({ ...prev, schedule: e.target.checked }))}
|
||||
className="text-indigo-600"
|
||||
/>
|
||||
<span className="ml-2">Предпочитания</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center ">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={mode.publishers2Import}
|
||||
onChange={(e) => setMode(prev => ({ ...prev, publishers2Import: e.target.checked }))}
|
||||
className="text-indigo-600"
|
||||
/>
|
||||
<span className="ml-2">Вестители</span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<button onClick={handleSave} className="bg-indigo-600 text-white py-2 px-4 rounded hover:bg-indigo-700">Запази</button>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 flex items-center space-x-4">
|
||||
<span className="text-gray-600">{data.length} вестители прочетени</span>
|
||||
<span className="text-gray-600">{status.info}</span>
|
||||
</div>
|
||||
|
||||
<table className="min-w-full border-collapse border border-gray-500">
|
||||
<thead>
|
||||
<tr>
|
||||
{data.length > 0 &&
|
||||
Object.keys(data[0]).map((key) => <th className="px-4 py-2 border-b font-medium" key={key}>{Object.values(data[0])[key]}</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.slice(1).map((row, index) => (
|
||||
<tr key={index} className="even:bg-gray-100">
|
||||
{Object.values(row).map((value, index) => (
|
||||
<td key={index} className="border px-4 py-2">{value}</td>
|
||||
))}
|
||||
<td id={row[1]}>
|
||||
<i className={`fa fa-circle ${status[row[3]] || 'text-gray-400'}`}></i>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ProtectedRoute>
|
||||
</Layout>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
214
pages/cart/publishers/index.tsx
Normal file
214
pages/cart/publishers/index.tsx
Normal file
@ -0,0 +1,214 @@
|
||||
// Next.js page to show all locations in the database with a link to the location page
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useEffect, useState, useRef, use } from "react";
|
||||
// import { getSession } from 'next-auth/client'
|
||||
// import { NextAuth } from 'next-auth/client'
|
||||
import { Publisher, UserRole } from "@prisma/client";
|
||||
import Layout from "../../../components/layout";
|
||||
import PublisherCard from "../../../components/publisher/PublisherCard";
|
||||
import axiosInstance from "../../../src/axiosSecure";
|
||||
import axiosServer from '../../../src/axiosServer';
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
import { levenshteinEditDistance } from "levenshtein-edit-distance";
|
||||
import ProtectedRoute from '../../../components/protectedRoute';
|
||||
import ConfirmationModal from '../../../components/ConfirmationModal';
|
||||
|
||||
|
||||
|
||||
interface IProps {
|
||||
initialItems: Publisher[];
|
||||
}
|
||||
|
||||
function PublishersPage({ publishers = [] }: IProps) {
|
||||
const [shownPubs, setShownPubs] = useState(publishers);
|
||||
const [filter, setFilter] = useState("");
|
||||
const [filterIsImported, setFilterIsImported] = useState({
|
||||
checked: false,
|
||||
indeterminate: true,
|
||||
});
|
||||
const [showZeroShiftsOnly, setShowZeroShiftsOnly] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const handleDeleteAllVisible = async () => {
|
||||
setIsDeleting(true);
|
||||
|
||||
for (const publisher of shownPubs) {
|
||||
try {
|
||||
await axiosInstance.delete(`/api/data/publishers/${publisher.id}`);
|
||||
setShownPubs(shownPubs.filter(p => p.id !== publisher.id));
|
||||
} catch (error) {
|
||||
console.log(JSON.stringify(error));
|
||||
}
|
||||
}
|
||||
|
||||
setIsDeleting(false);
|
||||
setIsModalOpen(false);
|
||||
// After all publishers are deleted, you might want to refresh the list or make additional changes
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// const filteredPublishers = publishers.filter((publisher) => {
|
||||
// return publisher.firstName.toLowerCase().includes(filter.toLowerCase())
|
||||
// || publisher.lastName.toLowerCase().includes(filter.toLowerCase());
|
||||
// });
|
||||
|
||||
//name filter
|
||||
let filteredPublishersByName = publishers
|
||||
.filter((publisher) => {
|
||||
const fullName = publisher.firstName.toLowerCase() + " " + publisher.lastName.toLowerCase();
|
||||
const distance = levenshteinEditDistance(fullName, filter.toLowerCase());
|
||||
const lenDiff = Math.max(fullName.length, filter.length) - Math.min(fullName.length, filter.length);
|
||||
|
||||
let similarity;
|
||||
if (distance === 0) {
|
||||
similarity = 1; // Exact match
|
||||
} else {
|
||||
similarity = 1 - (distance - lenDiff) / distance;
|
||||
}
|
||||
|
||||
console.log("distance: " + distance + "; lenDiff: " + lenDiff + " similarity: " + similarity + "; " + fullName + " =? " + filter + "")
|
||||
return similarity >= 0.95;
|
||||
});
|
||||
|
||||
// Email filter
|
||||
let filteredPublishersByEmail = publishers.filter(publisher =>
|
||||
publisher.email.toLowerCase().includes(filter.toLowerCase())
|
||||
);
|
||||
|
||||
// Combine name and email filters, removing duplicates
|
||||
let filteredPublishers = [...new Set([...filteredPublishersByName, ...filteredPublishersByEmail])];
|
||||
|
||||
// inactive publishers filter
|
||||
filteredPublishers = showZeroShiftsOnly
|
||||
? filteredPublishers.filter(p => p.assignments.length === 0)
|
||||
: filteredPublishers;
|
||||
|
||||
setShownPubs(filteredPublishers);
|
||||
}, [filter, showZeroShiftsOnly]);
|
||||
|
||||
const checkboxRef = useRef();
|
||||
|
||||
const renderPublishers = () => {
|
||||
if (shownPubs.length === 0) {
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<a
|
||||
className="btn"
|
||||
href="javascript:void(0);"
|
||||
onClick={() => {
|
||||
setFilter("");
|
||||
handleFilterChange({ target: { value: "" } });
|
||||
}}
|
||||
>
|
||||
Clear filters
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return shownPubs.map((publisher) => (
|
||||
<PublisherCard key={publisher.id} publisher={publisher} />
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
const handleFilterChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value, type, checked } = event.target;
|
||||
|
||||
// setFilter(event.target.value);
|
||||
if (type === 'text') {
|
||||
setFilter(value);
|
||||
} else if (type === 'checkbox') {
|
||||
// setFilterIsImported({ ...checkboxFilter, [name]: checked });
|
||||
const { checked, indeterminate } = checkboxRef.current;
|
||||
if (!checked && !indeterminate) {
|
||||
// Checkbox was unchecked, set it to indeterminate state
|
||||
checkboxRef.current.indeterminate = true;
|
||||
setFilterIsImported({ checked: false, indeterminate: true });
|
||||
} else if (!checked && indeterminate) {
|
||||
// Checkbox was indeterminate, set it to checked state
|
||||
checkboxRef.current.checked = true;
|
||||
checkboxRef.current.indeterminate = false;
|
||||
setFilterIsImported({ checked: true, indeterminate: false });
|
||||
} else if (checked && !indeterminate) {
|
||||
// Checkbox was checked, set it to unchecked state
|
||||
checkboxRef.current.checked = false;
|
||||
checkboxRef.current.indeterminate = false;
|
||||
setFilterIsImported({ checked: false, indeterminate: false });
|
||||
} else {
|
||||
// Checkbox was checked and indeterminate (should not happen), set it to unchecked state
|
||||
checkboxRef.current.checked = false;
|
||||
checkboxRef.current.indeterminate = false;
|
||||
setFilterIsImported({ checked: false, indeterminate: false });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<ProtectedRoute allowedRoles={[UserRole.ADMIN, UserRole.POWERUSER]}>
|
||||
<div className="">
|
||||
<div className="flex items-center justify-center space-x-4 m-4">
|
||||
<div className="flex justify-center m-4">
|
||||
<a href="/cart/publishers/new" className="btn"> Добави вестител </a>
|
||||
</div>
|
||||
|
||||
<button className="button m-2 btn btn-danger" onClick={() => setIsModalOpen(true)} disabled={isDeleting} >
|
||||
{isDeleting ? "Изтриване..." : "Изтрий показаните вестители"}
|
||||
</button>
|
||||
<ConfirmationModal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => setIsModalOpen(false)}
|
||||
onConfirm={handleDeleteAllVisible}
|
||||
message="Сигурни ли сте, че искате да изтриете всички показани в момента вестители?"
|
||||
/>
|
||||
<div className="flex justify-center m-4">
|
||||
<a href="/cart/publishers/import" className="btn"> Import publishers </a>
|
||||
</div>
|
||||
</div>
|
||||
<div name="filters" className="flex items-center justify-center space-x-4 m-4 sticky top-4 z-10 bg-gray-100 p-2">
|
||||
<label htmlFor="filter">Filter:</label>
|
||||
<input type="text" id="filter" name="filter" value={filter} onChange={handleFilterChange}
|
||||
className="border border-gray-300 rounded-md px-2 py-1"
|
||||
/>
|
||||
|
||||
|
||||
<label htmlFor="zeroShiftsOnly" className="ml-4 inline-flex items-center">
|
||||
<input type="checkbox" id="zeroShiftsOnly" checked={showZeroShiftsOnly}
|
||||
onChange={e => setShowZeroShiftsOnly(e.target.checked)}
|
||||
className="form-checkbox text-indigo-600"
|
||||
/>
|
||||
<span className="ml-2">само без смени</span>
|
||||
</label>
|
||||
|
||||
<span id="filter-info" className="ml-4">{publishers.length} от {publishers.length} вестителя</span>
|
||||
|
||||
</div>
|
||||
<div className="grid gap-4 grid-cols-1 md:grid-cols-4 z-0">
|
||||
{renderPublishers()}
|
||||
</div>
|
||||
</div>
|
||||
</ProtectedRoute>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default PublishersPage;
|
||||
|
||||
//import { set } from "date-fns";
|
||||
|
||||
export const getServerSideProps = async (context) => {
|
||||
const axios = await axiosServer(context);
|
||||
//ToDo: refactor all axios calls to use axiosInstance and this URL
|
||||
const { data: publishers } = await axios.get('/api/data/publishers?select=id,firstName,lastName,email,isactive,isTrained,isImported,assignments.shift.startTime,availabilities.startTime&dev=fromuseefect');
|
||||
|
||||
return {
|
||||
props: {
|
||||
publishers,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
59
pages/cart/publishers/new.tsx
Normal file
59
pages/cart/publishers/new.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
//next.js page to show all locatons in the database with a link to the location page
|
||||
import { useState } from "react";
|
||||
import { useRouter } from 'next/router';
|
||||
import Layout from "../../../components/layout";
|
||||
import axiosServer from '../../../src/axiosServer';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import ProtectedRoute from '../../../components/protectedRoute';
|
||||
import PublisherForm from "../../../components/publisher/PublisherForm";
|
||||
import { Publisher, UserRole } from "@prisma/client";
|
||||
|
||||
|
||||
export default function NewPubPage(item: Publisher) {
|
||||
item = item.item;
|
||||
const [publisher, setPublisher] = useState<Publisher>(item);
|
||||
const router = useRouter();
|
||||
const { id, self } = router.query;
|
||||
const { data: session, status } = useSession();
|
||||
|
||||
const userRole = session?.user?.role as UserRole;
|
||||
const userId = session?.user?.id;
|
||||
|
||||
// Check if the user is editing their own profile and adjust allowedRoles accordingly
|
||||
let allowedRoles = [UserRole.POWERUSER, UserRole.ADMIN];
|
||||
if (status === 'authenticated' && userId && userId === id) {
|
||||
allowedRoles.push(userRole);
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<ProtectedRoute allowedRoles={allowedRoles}>
|
||||
<div className="h-5/6 grid place-items-center">
|
||||
<PublisherForm key={item?.id} item={item} me={self} />
|
||||
</div>
|
||||
</ProtectedRoute>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
//------------------pages\cart\publishers\edit\[id].tsx------------------
|
||||
|
||||
export const getServerSideProps = async (context) => {
|
||||
const axios = await axiosServer(context);
|
||||
|
||||
context.res.setHeader("Cache-Control", "s-maxage=1, stale-while-revalidate");
|
||||
if (!context.query || !context.query.id) {
|
||||
return {
|
||||
props: {}
|
||||
};
|
||||
}
|
||||
var url = process.env.NEXTAUTH_URL + "/api/data/publishers/" + context.query.id + "?include=availabilities,shifts";
|
||||
console.log("GET PUBLISHER FROM:" + url)
|
||||
const { data } = await axios.get(url);
|
||||
|
||||
return {
|
||||
props: {
|
||||
data
|
||||
},
|
||||
};
|
||||
};
|
Reference in New Issue
Block a user