diff --git a/_doc/ToDo.md b/_doc/ToDo.md index 1bc87ef..8a37727 100644 --- a/_doc/ToDo.md +++ b/_doc/ToDo.md @@ -262,4 +262,12 @@ in schedule admin - if a publisher is always pair & family is not in the shift - [] allow blocking of inputs (different from publishing) TODO: fix to keep previous occurances when repeating evert week [] user - add createdAt field -[] FIX insecure logins \ No newline at end of file +[x] FIX insecure logins + +[] nove push to form, - reorganize pWAManager to have session, role, subscriptions, etc... +[] add shift name in calendar/ show in schedule if no assignments. +[] show unpublished schedule if admin + + + + diff --git a/components/PwaManager.tsx b/components/PwaManager.tsx index 464afda..852fd0d 100644 --- a/components/PwaManager.tsx +++ b/components/PwaManager.tsx @@ -6,7 +6,7 @@ import e from 'express'; import ProtectedRoute from './protectedRoute'; import { UserRole } from '@prisma/client'; -function PwaManager({ subs }) { +function PwaManager({ userId, subs }) { //ToDo: for iOS, try to use apn? https://github.com/node-apn/node-apn/blob/master/doc/apn.markdown const isSupported = () => 'Notification' in window && @@ -271,6 +271,37 @@ function PwaManager({ subs }) { { action: 'close', title: 'Затвори', icon: '❌' }] }) }); + + /* + await fetch('/api/notify', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + id: pub.id, + message: "Тестово съобщение", + title: "Това е тестово съобщение от https://sofia.mwitnessing.com", + actions: [ + { action: 'OK', title: 'OK', icon: '✅' }, + { action: 'close', title: 'Затвори', icon: '❌' } + ] + // actions: [ + // { + // title: 'Open URL', + // action: 'open_url', + // icon: '/images/open-url.png' + // }, + // { + // title: 'Dismiss', + // action: 'dismiss', + // icon: '/images/dismiss.png' + // } + // ] + }) + }) + */ + }; // async function sendTestReminder(event: MouseEvent): Promise { @@ -382,7 +413,7 @@ function PwaManager({ subs }) { > Тестово уведомление - + {isAdmin &&
{/*
} - {notificationPermission !== "granted" && ( - - )} + { + notificationPermission !== "granted" && ( + + ) + } - {isAdmin &&
-
- - Телеграм - Телеграм - + { + isAdmin &&
+
-
} ); diff --git a/components/publisher/PublisherForm.js b/components/publisher/PublisherForm.js index 7d76de3..85cdb6f 100644 --- a/components/publisher/PublisherForm.js +++ b/components/publisher/PublisherForm.js @@ -303,7 +303,8 @@ export default function PublisherForm({ item, me }) { {/* In-App notifications group */}

Известия в приложението

- + +
diff --git a/components/publisher/PublisherShiftsModal.js b/components/publisher/PublisherShiftsModal.js new file mode 100644 index 0000000..55d31d6 --- /dev/null +++ b/components/publisher/PublisherShiftsModal.js @@ -0,0 +1,128 @@ +//Refactor ToDo: show the whole month instead of just the current week by showing the shift start time in front of the rows, and show all shifts in the month from the first to the last week in the cell where we show one shift now +function PublisherShiftsModal({ publisher, shifts, onClose }) { + const monthInfo = common.getMonthDatesInfo(new Date(value)); + const monthShifts = shifts.filter(shift => { + const shiftDate = new Date(shift.startTime); + return shiftDate > monthInfo.firstDay && shiftDate < monthInfo.lastDay; + }); + const weekShifts = monthShifts.filter(shift => { + const shiftDate = new Date(shift.startTime); + return common.getStartOfWeek(value) <= shiftDate && shiftDate <= common.getEndOfWeek(value); + }); + const dayShifts = weekShifts.map(shift => { + const isAvailable = publisher.availabilities?.some(avail => + avail.startTime <= shift.startTime && avail.endTime >= shift.endTime + ); + let color = isAvailable ? getColorForShift(shift) : 'bg-gray-300'; + if (shift.isFromPreviousMonth) { + color += ' border-l-4 border-orange-500 '; + } + if (shift.isFromPreviousAssignment) { + color += ' border-l-4 border-red-500 '; + } + return { ...shift, isAvailable, color }; + }).reduce((acc, shift) => { + const dayIndex = new Date(shift.startTime).getDay(); + acc[dayIndex] = acc[dayIndex] || []; + acc[dayIndex].push(shift); + return acc; + }, {}); + console.log("dayShifts:", dayShifts); + + const hasAssignment = (shiftId) => { + // return publisher.assignments.some(ass => ass.shift.id == shiftId); + return publisher.assignments?.some(ass => { + //console.log(`Comparing: ${ass.shift.id} to ${shiftId}: ${ass.shift.id === shiftId}`); + return ass.shift.id === shiftId; + }); + }; + + + useEffect(() => { + const handleKeyDown = (event) => { + if (event.key === 'Escape') { + console.log('ESC: closing modal.'); + onClose(); // Call the onClose function when ESC key is pressed + } + }; + + // Add event listener + window.addEventListener('keydown', handleKeyDown); + + // Remove event listener on cleanup + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [onClose]); // Include onClose in the dependency array + + return ( +
+
+

График на + {publisher.firstName} {publisher.lastName} + {publisher.email} + тази седмица:

+ + {/* ... Display shifts in a calendar-like UI ... */} +
+ {Object.entries(dayShifts).map(([dayIndex, shiftsForDay]) => ( +
+ {/* Day header */} +
{new Date(shiftsForDay[0].startTime).getDate()}-ти
+ + {shiftsForDay.map((shift, index) => { + const assignmentExists = hasAssignment(shift.id); + const availability = publisher.availabilities.find(avail => + avail.startTime <= shift.startTime && avail.endTime >= shift.endTime + ); + const isFromPrevMonth = availability && availability.isFromPreviousMonth; + return ( +
+ {common.getTimeRange(shift.startTime, shift.endTime)} {shift.id} + + {!assignmentExists && shift.isAvailable && ( + + )} + {assignmentExists && ( + + )} +
+ ); + } + )} +
+ ))} +
+ + {/* Close button in the top right corner */} + + + {/* + + */} + {/* Edit button in the top right corner, next to the close button */} + + + + +
+
+ ); +} diff --git a/pages/api/schedule.ts b/pages/api/schedule.ts index d95b1e6..b723095 100644 --- a/pages/api/schedule.ts +++ b/pages/api/schedule.ts @@ -152,15 +152,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) //bold the text after - in the notes notes: notes, notes_bold: notes_bold, - names: shift.assignments - .map((assignment) => { - return ( - assignment.publisher.firstName + - " " + - assignment.publisher.lastName - ); - }) - .join(", "), + names: shift.assignments.length > 0 + ? shift.assignments + .map((assignment) => { + return ( + assignment.publisher.firstName + + " " + + assignment.publisher.lastName + ); + }) + .join(", ") + : shift.name, }; if (shiftSchedule.names.length > 0) { diff --git a/pages/cart/calendar/index.tsx b/pages/cart/calendar/index.tsx index fd55e48..0663bea 100644 --- a/pages/cart/calendar/index.tsx +++ b/pages/cart/calendar/index.tsx @@ -9,6 +9,7 @@ import Shift from '../../../components/calendar/ShiftComponent'; import { DayOfWeek, UserRole } from '@prisma/client'; import { env } from 'process' import ShiftComponent from '../../../components/calendar/ShiftComponent'; +import PublisherShiftsModal from '../../../components/publisher/PublisherShiftsModal'; //import { set } from 'date-fns'; const common = require('src/helpers/common'); import { toast } from 'react-toastify'; @@ -66,6 +67,7 @@ export default function CalendarPage({ initialEvents, initialShifts }) { const [shifts, setShifts] = React.useState([]); const [error, setError] = React.useState(null); const [availablePubs, setAvailablePubs] = React.useState([]); + const [selectedPublisher, setSelectedPublisher] = React.useState(null); const [selectedShiftId, setSelectedShiftId] = useState(null); @@ -214,8 +216,8 @@ export default function CalendarPage({ initialEvents, initialShifts }) { }; const handleSelectedPublisher = (publisher) => { - // Do something with the selected publisher console.log("handle pub clicked:", publisher); + setSelectedPublisher(publisher); } const handlePublisherModalOpen = async (publisher) => { // Do something with the selected publisher @@ -365,6 +367,10 @@ export default function CalendarPage({ initialEvents, initialShifts }) { isConfirmed: true }; const { data } = await axiosInstance.post("/api/data/assignments", newAssignment); + if (selectedShiftId == shiftId) { + handleShiftSelection(shifts.find(shift => shift.id === shiftId)); + } + // Update the 'publisher' property of the returned data with the full publisher object data.publisher = publisher; data.shift = shifts.find(shift => shift.id === shiftId); @@ -816,6 +822,7 @@ export default function CalendarPage({ initialEvents, initialShifts }) { ${bgAndBorderColorClass} ${selectedBorderClass} ${activeOpacityClass} ${pub.currentMonthAssignments >= pub.desiredShiftsPerMonth ? 'text-gray-400' : 'text-gray-800'}`} onDoubleClick={(handlePublisherModalOpen.bind(this, pub))} + onClick={handleSelectedPublisher.bind(this, pub)} > {pub.firstName} {pub.lastName} @@ -831,33 +838,8 @@ export default function CalendarPage({ initialEvents, initialShifts }) { @@ -905,133 +887,6 @@ export default function CalendarPage({ initialEvents, initialShifts }) { ); - function PublisherShiftsModal({ publisher, shifts, onClose }) { - const monthInfo = common.getMonthDatesInfo(new Date(value)); - const monthShifts = shifts.filter(shift => { - const shiftDate = new Date(shift.startTime); - return shiftDate > monthInfo.firstDay && shiftDate < monthInfo.lastDay; - }); - const weekShifts = monthShifts.filter(shift => { - const shiftDate = new Date(shift.startTime); - return common.getStartOfWeek(value) <= shiftDate && shiftDate <= common.getEndOfWeek(value); - }); - const dayShifts = weekShifts.map(shift => { - const isAvailable = publisher.availabilities?.some(avail => - avail.startTime <= shift.startTime && avail.endTime >= shift.endTime - ); - let color = isAvailable ? getColorForShift(shift) : 'bg-gray-300'; - if (shift.isFromPreviousMonth) { - color += ' border-l-4 border-orange-500 '; - } - if (shift.isFromPreviousAssignment) { - color += ' border-l-4 border-red-500 '; - } - return { ...shift, isAvailable, color }; - }).reduce((acc, shift) => { - const dayIndex = new Date(shift.startTime).getDay(); - acc[dayIndex] = acc[dayIndex] || []; - acc[dayIndex].push(shift); - return acc; - }, {}); - console.log("dayShifts:", dayShifts); - - const hasAssignment = (shiftId) => { - // return publisher.assignments.some(ass => ass.shift.id == shiftId); - return publisher.assignments?.some(ass => { - //console.log(`Comparing: ${ass.shift.id} to ${shiftId}: ${ass.shift.id === shiftId}`); - return ass.shift.id === shiftId; - }); - }; - - - useEffect(() => { - const handleKeyDown = (event) => { - if (event.key === 'Escape') { - console.log('ESC: closing modal.'); - onClose(); // Call the onClose function when ESC key is pressed - } - }; - - // Add event listener - window.addEventListener('keydown', handleKeyDown); - - // Remove event listener on cleanup - return () => { - window.removeEventListener('keydown', handleKeyDown); - }; - }, [onClose]); // Include onClose in the dependency array - - return ( -
-
-

График на - {publisher.firstName} {publisher.lastName} - {publisher.email} - тази седмица:

- - {/* ... Display shifts in a calendar-like UI ... */} -
- {Object.entries(dayShifts).map(([dayIndex, shiftsForDay]) => ( -
- {/* Day header */} -
{new Date(shiftsForDay[0].startTime).getDate()}-ти
- - {shiftsForDay.map((shift, index) => { - const assignmentExists = hasAssignment(shift.id); - const availability = publisher.availabilities.find(avail => - avail.startTime <= shift.startTime && avail.endTime >= shift.endTime - ); - const isFromPrevMonth = availability && availability.isFromPreviousMonth; - return ( -
- {common.getTimeRange(shift.startTime, shift.endTime)} {shift.id} - - {!assignmentExists && shift.isAvailable && ( - - )} - {assignmentExists && ( - - )} -
- ); - } - )} -
- ))} -
- - {/* Close button in the top right corner */} - - - {/* - - */} - {/* Edit button in the top right corner, next to the close button */} - - - - -
-
- ); - } function getColorForShift(shift) { const assignedCount = shift.assignedCount || 0; // Assuming each shift has an assignedCount property