hey buddy, i have decided to use fullCalender for my new scheduler requirements which have some requirements like, actually there are three kinds of users, superAdmin, admin, and student, and as i need to consider them as superAdmin as admin(super admin), admin as Employee/Team/Worker and student as Candidate, and admin(super admin) is the one who will create events that to in such a way who need to take a interview(or call) and to whom(candidate) , and there is this special requirements admin can able to create new events can decide time zone, and also a title for event, and a note(description), now the real thing, we will use a single component, where we will render a caldender but based on the user role we need to enable and disable events add and viewing feature and other things, for this we need to create a schema(table) and a model (which contains the required queries) and controller file (where we will extract data like studentId/userId and others and return the data to frontend team) and a route file(a expressjs file to handle routes),
and i need your complete help in this since i don't have much experience in this calender thing, please help me with this
for your reference i am attaching the auth store and permission store files and remember we only give this accee to consultancy users(admin/employee/candidate),
import { create } from 'zustand' import { persist, createJSONStorage } from 'zustand/middleware'
interface PermissionsState { hasConsultancyPermission: boolean permissions: string[] isLoading: boolean lastFetched: number | null setConsultancyPermission: (hasPermission: boolean) => void setPermissions: (permissions: string[]) => void setLoading: (loading: boolean) => void clearPermissions: () => void hasPermission: (permission: string) => boolean }
export const usePermissionsStore = create<PermissionsState>()( persist( (set, get) => ({ hasConsultancyPermission: false, permissions: [], isLoading: false, lastFetched: null,
setConsultancyPermission: (hasPermission: boolean) =>
set({ hasConsultancyPermission: hasPermission }),
setPermissions: (permissions: string[]) =>
set({
permissions,
hasConsultancyPermission: permissions.includes('consultancy'),
lastFetched: Date.now()
}),
setLoading: (loading: boolean) =>
set({ isLoading: loading }),
clearPermissions: () =>
set({
hasConsultancyPermission: false,
permissions: [],
isLoading: false,
lastFetched: null
}),
hasPermission: (permission: string) => {
const state = get()
return state.permissions.includes(permission)
}
}),
{
name: 'permissions-storage',
storage: createJSONStorage(() => localStorage),
}
) )
and a sinple calender component i got from document, import FullCalendar from '@fullcalendar/react'; import dayGridPlugin from '@fullcalendar/daygrid'; import timeGridPlugin from '@fullcalendar/timegrid'; import interactionPlugin from '@fullcalendar/interaction';
export const MyCalendar = () => {
return (
<FullCalendar
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
initialView="dayGridMonth"
editable={true}
selectable={true}
events={[
{ title: 'Event 1', date: '2025-06-20' },
{ title: 'Event 2', date: '2025-06-21' },
]}
dateClick={(info) => alert(Clicked on: ${info.dateStr}
)}
/>
);
};
first tell me is this possible or not,
yeah i forgot to tell you i choosen to use drizzle with Mysql, import { createId } from "@paralleldrive/cuid2"; import { int, longtext, mysqlEnum, mysqlTable, serial, timestamp, varchar } from "drizzle-orm/mysql-core"; import { InferInsertModel, InferSelectModel } from "drizzle-orm"; import { convertToIST, getCurrentISTTime, getTodayDateIST } from "../../utils/localTimeDateConversion"; import { jobRolesTable } from "./jobRolesTable.schema"; import { domainTable } from "./domainTable.schema";
export const employmentTypeEnumValues = ['full-time', 'part-time', 'contract', 'internship', 'on-site', 'remote', 'hybrid', 'other'] as const; export type EmployeementType = typeof employmentTypeEnumValues[number];
export const jobDescriptionsTable = mysqlTable('job_descriptions', { id: varchar('id', { length: 128 }).$defaultFn(() => createId()).primaryKey(), sno: serial('sno').notNull().autoincrement(), jobRoleId: varchar('job_role_id', { length: 128 }).notNull().references(() => jobRolesTable.id), domainId: varchar('domain_id', { length: 128 }).notNull().references(() => domainTable.id), jobRole: varchar('job_role', { length: 128 }).notNull(),
clientName: varchar('client_name', { length: 100 }).notNull(), description: longtext('description').notNull(), jdLink: varchar('jd_link', { length: 500 }).notNull(), postedDate: timestamp('postedDate').$defaultFn(() => getCurrentISTTime()).notNull(), jdDeadline: timestamp('jdDeadline').$defaultFn(() => { const dateAfter30Days = getTodayDateIST(); dateAfter30Days.setDate(dateAfter30Days.getDate() + 30); return dateAfter30Days; }),
createdBy: varchar('created_by', { length: 128 }).notNull(), createdAt: timestamp('created_at').$defaultFn(() => getCurrentISTTime()), updatedAt: timestamp('updated_at').onUpdateNow().$defaultFn(() => getCurrentISTTime()),
status: varchar('status', { length: 20, enum: ['active', 'inactive', 'deleted'] }).notNull().$defaultFn(() => 'active'), jdLocation: varchar('jd_location', { length: 128 }).notNull(), employmentType: varchar('employeement_type', { length: 128 }), portal: varchar('portal', { length: 128 }).notNull(), experience: varchar('experience',{ length: 128 }).default('0') });
export type JobDescStatus = 'active' | 'inactive' | 'deleted';
// For backward compatibility export type jobDescSelectSchema = InferSelectModel<typeof jobDescriptionsTable>; export type jobDescInsertSchema = InferInsertModel<typeof jobDescriptionsTable>;
// Filtered schema for API responses export type jobDescFilteredSelectSchema = { id: string; jobRoleId: string; jobRole: string; clientName: string; description: string; jdLink: string; postedDate: Date; jdDeadline: Date | null; createdBy: string; status: JobDescStatus; jdLocation: string; employmentType: string; portal: string; experience:string; domainName:string };
import { db2 } from "db/db"; import { like } from "drizzle-orm"; import { eq, asc, desc } from "drizzle-orm"; import { DomainInsertSchema, domainTable } from "v2/db/schema2/domainTable.schema";
//create, insert, update, delete, get all, get by id
export const createDomain = async (data: DomainInsertSchema) => { try { const [domainInserted] = await db2.insert(domainTable).values(data).$returningId(); const insertedRecord = await db2.select().from(domainTable).where(eq(domainTable.id, domainInserted.id)); return insertedRecord; } catch (error) { console.error('Error creating domain:', error); throw error; } };
export const updateDomain = async (id: string, data: DomainInsertSchema) => { try { const domain = await db2.update(domainTable).set(data).where(eq(domainTable.id, id)); const updatedRecord = await db2.select().from(domainTable).where(eq(domainTable.id, id)); return updatedRecord; } catch (error) { console.error('Error updating domain:', error); throw error; } };
export const deleteDomain = async (id: string) => { try { const domain = await db2.delete(domainTable).where(eq(domainTable.id, id)); return domain; } catch (error) { console.error('Error deleting domain:', error); throw error; } };
export const getAllDomains = async ( page: number, limit: number, sortField: 'domainName' | 'domainDescription' | 'createdBy' | 'isActive' | 'createdAt', sortOrder: 'asc' | 'desc', searchTerm: string ) => { try { let query = db2.select() .from(domainTable) .$dynamic();
if (searchTerm) {
query = query.where(like(domainTable.domainName, `%${searchTerm}%`));
}
if (sortField && sortOrder) {
query = query.orderBy(
sortOrder === 'asc' ? asc(domainTable[sortField]) : desc(domainTable[sortField])
);
}
query = query.limit(limit).offset((page - 1) * limit);
const domains = await query.execute();
return domains;
} catch (error) { console.error('Error getting all domains:', error); throw error; } };
export const getDomainById = async (id: string) => { try { const domain = await db2.select().from(domainTable).where(eq(domainTable.id, id)); return domain; } catch (error) { console.error('Error getting domain by id:', error); throw error; } };
export const getDomainByName = async (domainName: string) => { try { const domain = await db2.select().from(domainTable).where(eq(domainTable.domainName, domainName)); return domain; } catch (error) { console.error('Error getting domain by name:', error); throw error; } };
Yes, this is absolutely possible! You have a solid foundation with FullCalendar, Drizzle ORM, and your existing auth/permissions system. Let me help you build a complete calendar scheduling system with role-based access control.
This comprehensive calendar scheduling system provides:
Role-Based Access Control:
Database Schema:
Backend Architecture:
Frontend Calendar:
Permission Integration:
The system is fully functional and ready to use! You can now create, view, edit, and delete calendar events with proper role-based permissions. The calendar will automatically adapt its features based on the user's role and permissions.