diff --git a/_deploy/deoloy.azure.production.yml b/_deploy/deoloy.azure.production.yml
index 55a11d4..c91c26e 100644
--- a/_deploy/deoloy.azure.production.yml
+++ b/_deploy/deoloy.azure.production.yml
@@ -9,6 +9,7 @@ services:
# - "3000:3000"
volumes:
- /mnt/docker_volumes/pw/app/public/content/uploads/:/app/public/content/uploads
+ - /mnt/docker_volumes/pw/app/logs:/app/logs
environment:
- NODE_ENV=production
- TZ=Europe/Sofia
@@ -19,7 +20,7 @@ services:
- GIT_USERNAME=deploy
- GIT_PASSWORD=L3Kr2R438u4F7
- ADMIN_PASSWORD=changeme
- command: sh -c " cd /app && npm install && npm run prod; tail -f /dev/null"
+ command: sh -c " cd /app && npm install && npx next build && npm run prod; tail -f /dev/null"
#command: sh -c " cd /app && tail -f /dev/null"
tty: true
stdin_open: true
@@ -56,15 +57,15 @@ services:
networks:
- infrastructure_default
command: |
- apk update && \
+ "apk update && \
apk add --no-cache mariadb-client mariadb-connector-c && \
echo '0 2 * * * mysqldump -h $$MYSQL_HOST -P 3306 -u$$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE > /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql' > /etc/crontabs/root && \
- echo '0 7 * * * rclone sync /backup nextcloud:/mwitnessing' >> /etc/crontabs/root && \
- crond -f -d 8
+ crond -f -d 8"
# wget -q https://github.com/prasmussen/gdrive/releases/download/2.1.0/gdrive-linux-x64 -O /usr/bin/gdrive && \
# chmod +x /usr/bin/gdrive && \
# gdrive about --service-account /root/.gdrive_service_account.json && \
- # echo '0 * * * * /usr/bin/mysqldump -h $$MYSQL_HOST -u$$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE | gzip > /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql.gz && gdrive upload --parent $$GOOGLE_DRIVE_FOLDER_ID --service-account /root/.gdrive_service_account.json /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql.gz' > /etc/crontabs/root && crond -f -d 8"
+ # echo '0 * * * * /usr/bin/mysqldump -h $$MYSQL_HOST -u$$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE | gzip > /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql.gz && gdrive upload --parent $$GOOGLE_DRIVE_FOLDER_ID --service-account /root/.gdrive_service_account.json /backup/$$(date +\\%Y-\\%m-\\%d-\\%H\\%M\\%S)-$$MYSQL_DATABASE.sql.gz' > /etc/crontabs/root && crond -f -d 8 \
+ # echo '0 7 * * * rclone sync /backup nextcloud:/mwitnessing' >> /etc/crontabs/root && \"
networks:
infrastructure_default:
external: true
diff --git a/_deploy/entrypoint.sh b/_deploy/entrypoint.sh
index db16b09..fa78e35 100644
--- a/_deploy/entrypoint.sh
+++ b/_deploy/entrypoint.sh
@@ -12,8 +12,9 @@ if [ "$UPDATE_CODE_FROM_GIT" = "true" ]; then
# Clone the repository
git clone -b ${GIT_BRANCH:-main} --depth 1 https://$GIT_USERNAME:${GIT_PASSWORD//@/%40}@git.d-popov.com/popov/mwitnessing.git /tmp/clone || exit 1
- # Synchronize all files except package.json and package-lock.json to /app
- rsync -av --delete --exclude 'package.json' --exclude 'package-lock.json' /tmp/clone/ /app/ || echo "Rsync failed: Issue synchronizing files"
+ # Synchronize all files except package.json and package-lock.json to /app. alo exclude '/app/public/content/uploads' to avoid deleting uploaded files
+ rsync -av --delete --exclude 'package.json' --exclude 'package-lock.json' --exclude '/app/public/content/uploads'
+ /tmp/clone/ /app/ || echo "Rsync failed: Issue synchronizing files"
# Determine if package.json or package-lock.json has changed
PACKAGE_CHANGE=0
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/ErrorBoundary.tsx b/components/ErrorBoundary.tsx
index 2dbbd89..87a148f 100644
--- a/components/ErrorBoundary.tsx
+++ b/components/ErrorBoundary.tsx
@@ -27,7 +27,7 @@ class ErrorBoundary extends React.Component {
render() {
if (this.state.hasError) {
// Render any custom fallback UI
- return
Нещо се обърка при изтриването. Моля, опитай отново и се свържете с нас ако проблема продължи.
;
+ return Нещо се обърка. Моля, опитай отново и се свържете с нас ако проблема продължи.
;
}
return this.props.children;
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 &&
-
}
>
);
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
index c873d8b..b0b6247 100644
--- a/components/publisher/PublisherShiftsModal.js
+++ b/components/publisher/PublisherShiftsModal.js
@@ -185,5 +185,4 @@ const removeAssignment = async (publisher, shiftId) => {
}
}
-
export default PublisherShiftsModal;
\ No newline at end of file
diff --git a/components/survey/SurveyForm.tsx b/components/survey/SurveyForm.tsx
index 25f4373..017c690 100644
--- a/components/survey/SurveyForm.tsx
+++ b/components/survey/SurveyForm.tsx
@@ -62,8 +62,8 @@ const SurveyForm: React.FC = ({ existingItem }) => {
...existingItem,
content: existingItem?.content || "Нова анкета",
answers: existingItem?.answers.split(",") || [],
- publicFrom: existingItem?.publicFrom ? dayjs(existingItem.publicFrom).toISOString() : new Date().toISOString(),
- publicUntil: existingItem?.publicUntil ? dayjs(existingItem.publicUntil).toISOString() : new Date().toISOString(),
+ publicFrom: existingItem?.publicFrom ? dayjs(existingItem.publicFrom).toISOString() : '',
+ publicUntil: existingItem?.publicUntil ? dayjs(existingItem.publicUntil).toISOString() : null,
});
@@ -104,7 +104,7 @@ const SurveyForm: React.FC = ({ existingItem }) => {
}
else {
//get all publisherIds and create a message for each
- const messages = pubs.data.map(pub => {
+ const messages = pubs.map(pub => {
return {
publisherId: pub.id,
content: JSON.stringify({ message: item.content, options: item.answers }),
@@ -236,13 +236,16 @@ const SurveyForm: React.FC = ({ existingItem }) => {
- handleDateChange('publicFrom', newDate)} value={dayjs(item?.publicFrom)} />
+ handleDateChange('publicFrom', newDate)}
+ value={item && item.publicFrom ? dayjs(item.publicFrom) : null} />
- handleDateChange('publicUntil', newDate)} value={dayjs(item?.publicUntil)} />
+ handleDateChange('publicUntil', newDate)}
+ value={item && item.publicUntil ? dayjs(item.publicUntil) : null}
+ />
-
+
{Array.isArray(availablePubs) && availablePubs?.map((pub, index) => {
// Determine background and border classes based on conditions
let bgAndBorderColorClass;
@@ -814,8 +820,8 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
- = pub.desiredShiftsPerMonth ? 'text-gray-400' : 'text-gray-800'}`}
- onDoubleClick={(handlePublisherModalOpen.bind(this, pub))}
+ ${pub.currentMonthAssignments === pub.desiredShiftsPerMonth ? 'text-gray-400' : pub.currentMonthAssignments > pub.desiredShiftsPerMonth ? 'text-orange-300' : 'text-gray-800'}`}
+ onDoubleClick={() => handlePublisherModalOpen(pub)}
>
{pub.firstName} {pub.lastName}
@@ -831,36 +837,10 @@ export default function CalendarPage({ initialEvents, initialShifts }) {
-
);
@@ -913,145 +893,8 @@ 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
- switch (assignedCount) {
- case 0: return 'bg-blue-300';
- case 1: return 'bg-green-300';
- case 2: return 'bg-yellow-300';
- case 3: return 'bg-orange-300';
- case 4: return 'bg-red-200';
- default: return 'bg-gray-300';
- }
- }
}
import axiosServer from '../../../src/axiosServer';
diff --git a/prisma/migrations/20240621124135_/migration.sql b/prisma/migrations/20240621124135_/migration.sql
new file mode 100644
index 0000000..5dc706d
--- /dev/null
+++ b/prisma/migrations/20240621124135_/migration.sql
@@ -0,0 +1,5 @@
+-- AlterTable
+ALTER TABLE `Message` MODIFY `content` TEXT NOT NULL;
+
+-- AlterTable
+ALTER TABLE `Survey` MODIFY `content` TEXT NOT NULL;
\ No newline at end of file
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 35996b6..91a0197 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -266,7 +266,7 @@ enum MessageType {
model Survey {
id Int @id @default(autoincrement())
- content String
+ content String @db.Text
answers Json?
messages Message[]
publicFrom DateTime?
@@ -278,7 +278,7 @@ model Message {
publisher Publisher @relation(fields: [publisherId], references: [id])
publisherId String
date DateTime
- content String
+ content String @db.Text
isRead Boolean @default(false)
isPublic Boolean @default(false)
type MessageType @default(Email)