Compare commits
17 Commits
homepage/w
...
1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 3180097aa7 | |||
| 4476dd6d74 | |||
| 9ec6d65a4d | |||
| 65f8527cc5 | |||
| bded69345f | |||
| 6f7a0bf3b2 | |||
| 50a94ae86f | |||
| 5b61687cd9 | |||
| 1cf6b35021 | |||
| 18d08af91d | |||
| a95dbe2623 | |||
| 6f3ab2da73 | |||
| 8f397131d4 | |||
| 9139a06f4c | |||
| a7c2ed7dff | |||
| 637968552d | |||
| 9953e2765a |
13
api/package-lock.json
generated
13
api/package-lock.json
generated
@@ -10,6 +10,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/node": "^10.27.0",
|
"@sentry/node": "^10.27.0",
|
||||||
|
"chalk": "^5.6.2",
|
||||||
"connect-sqlite3": "^0.9.16",
|
"connect-sqlite3": "^0.9.16",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
@@ -1314,6 +1315,18 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chalk": {
|
||||||
|
"version": "5.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||||
|
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/node": "^10.27.0",
|
"@sentry/node": "^10.27.0",
|
||||||
|
"chalk": "^5.6.2",
|
||||||
"connect-sqlite3": "^0.9.16",
|
"connect-sqlite3": "^0.9.16",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
|
|||||||
@@ -5,8 +5,26 @@ import express = require('express');
|
|||||||
import cors = require('cors');
|
import cors = require('cors');
|
||||||
import morgan = require('morgan');
|
import morgan = require('morgan');
|
||||||
const app = express()
|
const app = express()
|
||||||
app.use(morgan('dev', {
|
import chalk from 'chalk';
|
||||||
skip: (req) => {
|
app.use(morgan((tokens: morgan.TokenIndexer, req: express.Request, res: express.Response) => {
|
||||||
|
const status = Number(tokens.status(req, res));
|
||||||
|
|
||||||
|
// Colorize status code
|
||||||
|
const statusColor = status >= 500 ? chalk.red
|
||||||
|
: status >= 400 ? chalk.yellow
|
||||||
|
: status >= 300 ? chalk.cyan
|
||||||
|
: chalk.green;
|
||||||
|
|
||||||
|
return [
|
||||||
|
chalk.gray(`[${new Date().toISOString()}]`),
|
||||||
|
chalk.blue.bold(tokens.method(req, res)),
|
||||||
|
tokens.url(req, res),
|
||||||
|
statusColor(status),
|
||||||
|
chalk.magenta(tokens['response-time'](req, res) + ' ms'),
|
||||||
|
chalk.yellow(`- User: ${req.user?.name ? `${req.user.name} (${req.user.id})` : 'Unauthenticated'}`),
|
||||||
|
].join(' ');
|
||||||
|
}, {
|
||||||
|
skip: (req: express.Request) => {
|
||||||
return req.originalUrl === '/members/me';
|
return req.originalUrl === '/members/me';
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ router.get('/me/:id', [requireLogin], async (req: Request, res: Response) => {
|
|||||||
const application = await getApplicationByID(appID);
|
const application = await getApplicationByID(appID);
|
||||||
if (application === undefined)
|
if (application === undefined)
|
||||||
return res.sendStatus(204);
|
return res.sendStatus(204);
|
||||||
console.log(application.member_id, member)
|
|
||||||
if (application.member_id != member) {
|
if (application.member_id != member) {
|
||||||
return res.sendStatus(403);
|
return res.sendStatus(403);
|
||||||
}
|
}
|
||||||
@@ -191,8 +190,6 @@ router.post('/:id/comment', [requireLogin], async (req: Request, res: Response)
|
|||||||
const data = req.body.message;
|
const data = req.body.message;
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
|
|
||||||
console.log(user)
|
|
||||||
|
|
||||||
const sql = `INSERT INTO application_comments(
|
const sql = `INSERT INTO application_comments(
|
||||||
application_id,
|
application_id,
|
||||||
poster_id,
|
poster_id,
|
||||||
@@ -205,7 +202,6 @@ VALUES(?, ?, ?);`
|
|||||||
var conn = await pool.getConnection();
|
var conn = await pool.getConnection();
|
||||||
|
|
||||||
const result = await conn.query(sql, [appID, user.id, data])
|
const result = await conn.query(sql, [appID, user.id, data])
|
||||||
console.log(result)
|
|
||||||
if (result.affectedRows !== 1) {
|
if (result.affectedRows !== 1) {
|
||||||
conn.release();
|
conn.release();
|
||||||
throw new Error("Insert Failure")
|
throw new Error("Insert Failure")
|
||||||
@@ -237,8 +233,6 @@ router.post('/:id/adminComment', [requireLogin, requireRole("Recruiter")], async
|
|||||||
const data = req.body.message;
|
const data = req.body.message;
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
|
|
||||||
console.log(user)
|
|
||||||
|
|
||||||
const sql = `INSERT INTO application_comments(
|
const sql = `INSERT INTO application_comments(
|
||||||
application_id,
|
application_id,
|
||||||
poster_id,
|
poster_id,
|
||||||
@@ -251,7 +245,6 @@ VALUES(?, ?, ?, 1);`
|
|||||||
var conn = await pool.getConnection();
|
var conn = await pool.getConnection();
|
||||||
|
|
||||||
const result = await conn.query(sql, [appID, user.id, data])
|
const result = await conn.query(sql, [appID, user.id, data])
|
||||||
console.log(result)
|
|
||||||
if (result.affectedRows !== 1) {
|
if (result.affectedRows !== 1) {
|
||||||
conn.release();
|
conn.release();
|
||||||
throw new Error("Insert Failure")
|
throw new Error("Insert Failure")
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ r.put('/', [requireLogin, requireMemberState(MemberState.Member)], async (req: R
|
|||||||
let event: CalendarEvent = req.body;
|
let event: CalendarEvent = req.body;
|
||||||
event.start = new Date(event.start);
|
event.start = new Date(event.start);
|
||||||
event.end = new Date(event.end);
|
event.end = new Date(event.end);
|
||||||
console.log(event);
|
|
||||||
updateEvent(event);
|
updateEvent(event);
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ er.get('/attendees/:id', async (req: Request, res: Response) => {
|
|||||||
er.post('/', async (req: Request, res: Response) => {
|
er.post('/', async (req: Request, res: Response) => {
|
||||||
const posterID: number = req.user.id;
|
const posterID: number = req.user.id;
|
||||||
try {
|
try {
|
||||||
console.log();
|
|
||||||
let data: CourseEventDetails = req.body;
|
let data: CourseEventDetails = req.body;
|
||||||
data.created_by = posterID;
|
data.created_by = posterID;
|
||||||
data.event_date = new Date(data.event_date);
|
data.event_date = new Date(data.event_date);
|
||||||
|
|||||||
@@ -30,9 +30,6 @@ router.post("/admin", [requireRole("17th Administrator")], async (req: Request,
|
|||||||
let LOARequest = req.body as LOARequest;
|
let LOARequest = req.body as LOARequest;
|
||||||
LOARequest.created_by = req.user.id;
|
LOARequest.created_by = req.user.id;
|
||||||
LOARequest.filed_date = new Date();
|
LOARequest.filed_date = new Date();
|
||||||
|
|
||||||
console.log(LOARequest);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await createNewLOA(LOARequest);
|
await createNewLOA(LOARequest);
|
||||||
res.sendStatus(201);
|
res.sendStatus(201);
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ router.get('/me', [requireLogin], async (req, res) => {
|
|||||||
router.get('/settings', [requireLogin], async (req: Request, res: Response) => {
|
router.get('/settings', [requireLogin], async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
let user = req.user.id;
|
let user = req.user.id;
|
||||||
console.log(user);
|
|
||||||
let output = await getMemberSettings(user);
|
let output = await getMemberSettings(user);
|
||||||
res.status(200).json(output);
|
res.status(200).json(output);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -66,7 +65,6 @@ router.put('/settings', [requireLogin], async (req: Request, res: Response) => {
|
|||||||
try {
|
try {
|
||||||
let user = req.user.id;
|
let user = req.user.id;
|
||||||
let settings: memberSettings = req.body;
|
let settings: memberSettings = req.body;
|
||||||
console.log(settings)
|
|
||||||
await setUserSettings(user, settings);
|
await setUserSettings(user, settings);
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ ur.post('/', [requireMemberState(MemberState.Member), requireRole("17th Administ
|
|||||||
ur.delete('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
|
ur.delete('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const body = req.body;
|
const body = req.body;
|
||||||
console.log(body);
|
|
||||||
|
|
||||||
const sql = 'DELETE FROM members_roles WHERE member_id = ? AND role_id = ?'
|
const sql = 'DELETE FROM members_roles WHERE member_id = ? AND role_id = ?'
|
||||||
await pool.query(sql, [body.member_id, body.role_id])
|
await pool.query(sql, [body.member_id, body.role_id])
|
||||||
@@ -86,7 +85,6 @@ r.get('/', [requireMemberState(MemberState.Member)], async (req, res) => {
|
|||||||
r.post('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
|
r.post('/', [requireMemberState(MemberState.Member), requireRole("17th Administrator")], async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { name, color, description } = req.body;
|
const { name, color, description } = req.body;
|
||||||
console.log('Creating role:', { name, color, description });
|
|
||||||
if (!name || !color) {
|
if (!name || !color) {
|
||||||
return res.status(400).json({ error: 'Name and color are required' });
|
return res.status(400).json({ error: 'Name and color are required' });
|
||||||
}
|
}
|
||||||
@@ -113,7 +111,7 @@ r.delete('/:id', [requireMemberState(MemberState.Member), requireRole("17th Admi
|
|||||||
const res = await pool.query(sql, [id]);
|
const res = await pool.query(sql, [id]);
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
res.sendStatus(500);
|
res.sendStatus(500);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ export async function approveApplication(id: number, approver: number) {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const result = await pool.execute(sql, [approver, id]);
|
const result = await pool.execute(sql, [approver, id]);
|
||||||
console.log(result);
|
|
||||||
if (result.affectedRows == 1) {
|
if (result.affectedRows == 1) {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@@ -91,7 +90,6 @@ export async function denyApplication(id: number, approver: number) {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const result = await pool.execute(sql, [approver, id]);
|
const result = await pool.execute(sql, [approver, id]);
|
||||||
console.log(result);
|
|
||||||
if (result.affectedRows == 1) {
|
if (result.affectedRows == 1) {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -126,6 +126,5 @@ export async function getEventAttendance(eventID: number): Promise<CalendarSignu
|
|||||||
|
|
||||||
const sql = "CALL `sp_GetCalendarEventSignups`(?)"
|
const sql = "CALL `sp_GetCalendarEventSignups`(?)"
|
||||||
const res = await pool.query(sql, [eventID]);
|
const res = await pool.query(sql, [eventID]);
|
||||||
console.log(res[0]);
|
|
||||||
return res[0];
|
return res[0];
|
||||||
}
|
}
|
||||||
@@ -89,13 +89,11 @@ export async function closeLOA(id: number, closer: number) {
|
|||||||
ended_at = NOW()
|
ended_at = NOW()
|
||||||
WHERE leave_of_absences.id = ?`;
|
WHERE leave_of_absences.id = ?`;
|
||||||
let out = await pool.query(sql, [closer, id]);
|
let out = await pool.query(sql, [closer, id]);
|
||||||
console.log(out);
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getLOAbyID(id: number): Promise<LOARequest> {
|
export async function getLOAbyID(id: number): Promise<LOARequest> {
|
||||||
let res = await pool.query(`SELECT * FROM leave_of_absences WHERE id = ?`, [id]);
|
let res = await pool.query(`SELECT * FROM leave_of_absences WHERE id = ?`, [id]);
|
||||||
console.log(res);
|
|
||||||
if (res.length != 1)
|
if (res.length != 1)
|
||||||
throw new Error(`LOA with id ${id} not found`);
|
throw new Error(`LOA with id ${id} not found`);
|
||||||
return res[0];
|
return res[0];
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ export async function setUserSettings(id: number, settings: memberSettings) {
|
|||||||
displayName = ?
|
displayName = ?
|
||||||
WHERE id = ?;`;
|
WHERE id = ?;`;
|
||||||
let result = await pool.query(sql, [settings.displayName, id])
|
let result = await pool.query(sql, [settings.displayName, id])
|
||||||
console.log(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMembersLite(ids: number[]): Promise<MemberLight[]> {
|
export async function getMembersLite(ids: number[]): Promise<MemberLight[]> {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
export function toDateTime(date: Date): string {
|
export function toDateTime(date: Date): string {
|
||||||
console.log(date);
|
|
||||||
if (typeof date === 'string') {
|
if (typeof date === 'string') {
|
||||||
date = new Date(date);
|
date = new Date(date);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ export type Role = {
|
|||||||
const addr = import.meta.env.VITE_APIHOST;
|
const addr = import.meta.env.VITE_APIHOST;
|
||||||
|
|
||||||
export async function getRoles(): Promise<Role[]> {
|
export async function getRoles(): Promise<Role[]> {
|
||||||
const res = await fetch(`${addr}/roles`)
|
const res = await fetch(`${addr}/roles`, {
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
return res.json() as Promise<Role[]>;
|
return res.json() as Promise<Role[]>;
|
||||||
@@ -26,11 +28,12 @@ export async function createRole(name: string, color: string, description: strin
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
|
credentials: 'include',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name,
|
name,
|
||||||
color,
|
color,
|
||||||
description
|
description
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -47,6 +50,7 @@ export async function addMemberToRole(member_id: number, role_id: number): Promi
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
|
credentials: 'include',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
member_id,
|
member_id,
|
||||||
role_id
|
role_id
|
||||||
@@ -64,6 +68,7 @@ export async function addMemberToRole(member_id: number, role_id: number): Promi
|
|||||||
export async function removeMemberFromRole(member_id: number, role_id: number): Promise<boolean> {
|
export async function removeMemberFromRole(member_id: number, role_id: number): Promise<boolean> {
|
||||||
const res = await fetch(`${addr}/memberRoles`, {
|
const res = await fetch(`${addr}/memberRoles`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
},
|
},
|
||||||
@@ -83,7 +88,8 @@ export async function removeMemberFromRole(member_id: number, role_id: number):
|
|||||||
|
|
||||||
export async function deleteRole(role_id: number): Promise<boolean> {
|
export async function deleteRole(role_id: number): Promise<boolean> {
|
||||||
const res = await fetch(`${addr}/roles/${role_id}`, {
|
const res = await fetch(`${addr}/roles/${role_id}`, {
|
||||||
method: "DELETE"
|
method: "DELETE",
|
||||||
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ const { handleSubmit, values, resetForm } = useForm({
|
|||||||
const formSubmitted = ref(false);
|
const formSubmitted = ref(false);
|
||||||
|
|
||||||
const onSubmit = handleSubmit(async (values) => {
|
const onSubmit = handleSubmit(async (values) => {
|
||||||
console.log(values);
|
|
||||||
const out: LOARequest = {
|
const out: LOARequest = {
|
||||||
member_id: values.member_id,
|
member_id: values.member_id,
|
||||||
start_date: values.start_date,
|
start_date: values.start_date,
|
||||||
@@ -122,7 +121,7 @@ const minEndDate = computed(() => {
|
|||||||
if (values.start_date) {
|
if (values.start_date) {
|
||||||
return new CalendarDate(values.start_date.getFullYear(), values.start_date.getMonth() + 1, values.start_date.getDate())
|
return new CalendarDate(values.start_date.getFullYear(), values.start_date.getMonth() + 1, values.start_date.getDate())
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return today(getLocalTimeZone());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -135,6 +134,17 @@ const maxEndDate = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const minStartDate = computed(() => {
|
||||||
|
if (values.type && values.end_date) {
|
||||||
|
let endDateObj = new Date(values.end_date.getTime() - values.type.max_length_days * 24 * 60 * 60 * 1000);
|
||||||
|
let td = today(getLocalTimeZone());
|
||||||
|
let start = new CalendarDate(endDateObj.getFullYear(), endDateObj.getMonth() + 1, endDateObj.getDate())
|
||||||
|
return td.compare(start) > 0 ? td : start;
|
||||||
|
} else {
|
||||||
|
return today(getLocalTimeZone());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const memberFilter = ref('');
|
const memberFilter = ref('');
|
||||||
|
|
||||||
const filteredMembers = computed(() => {
|
const filteredMembers = computed(() => {
|
||||||
@@ -227,21 +237,31 @@ const filteredMembers = computed(() => {
|
|||||||
<FieldContent>
|
<FieldContent>
|
||||||
<FieldLabel>Start Date</FieldLabel>
|
<FieldLabel>Start Date</FieldLabel>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger as-child>
|
<div class="relative inline-flex items-center group">
|
||||||
<Button variant="outline" :class="cn(
|
<PopoverTrigger as-child>
|
||||||
'w-full justify-start text-left font-normal',
|
<Button :disabled="!values.type" variant="outline" :class="cn(
|
||||||
!field.value && 'text-muted-foreground',
|
'w-full justify-start text-left font-normal',
|
||||||
)">
|
!field.value && 'text-muted-foreground',
|
||||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
)">
|
||||||
{{ field.value ? df.format(field.value) : "Pick a date" }}
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
</Button>
|
{{ field.value ? df.format(field.value) : "Pick a date" }}
|
||||||
</PopoverTrigger>
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<!-- Tooltip bubble -->
|
||||||
|
<div v-if="!values?.type" class="pointer-events-none absolute -top-9 left-1/2 -translate-x-1/2
|
||||||
|
whitespace-nowrap rounded-md bg-popover px-2 py-1 text-xs
|
||||||
|
text-popover-foreground shadow-md border border-border
|
||||||
|
opacity-0 translate-y-1
|
||||||
|
group-hover:opacity-100 group-hover:translate-y-0
|
||||||
|
transition-opacity transition-transform duration-150">
|
||||||
|
Select an LOA type first
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<PopoverContent class="w-auto p-0">
|
<PopoverContent class="w-auto p-0">
|
||||||
<Calendar
|
<Calendar
|
||||||
:model-value="field.value
|
|
||||||
? new CalendarDate(field.value.getFullYear(), field.value.getMonth() + 1, field.value.getDate()) : null"
|
|
||||||
@update:model-value="(val: CalendarDate) => field.onChange(val.toDate(getLocalTimeZone()))"
|
@update:model-value="(val: CalendarDate) => field.onChange(val.toDate(getLocalTimeZone()))"
|
||||||
layout="month-and-year" :min-value="today(getLocalTimeZone())" />
|
layout="month-and-year"
|
||||||
|
:min-value="minStartDate || today(getLocalTimeZone())" />
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
<div class="h-4">
|
<div class="h-4">
|
||||||
@@ -255,18 +275,28 @@ const filteredMembers = computed(() => {
|
|||||||
<FieldContent>
|
<FieldContent>
|
||||||
<FieldLabel>End Date</FieldLabel>
|
<FieldLabel>End Date</FieldLabel>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger as-child>
|
<div class="relative inline-flex items-center group">
|
||||||
<Button variant="outline" :class="cn(
|
<PopoverTrigger as-child>
|
||||||
'w-full justify-start text-left font-normal',
|
<Button :disabled="!values.type" variant="outline" :class="cn(
|
||||||
!field.value && 'text-muted-foreground',
|
'w-full justify-start text-left font-normal',
|
||||||
)">
|
!field.value && 'text-muted-foreground',
|
||||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
)">
|
||||||
{{ field.value ? df.format(field.value) : "Pick a date" }}
|
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||||
</Button>
|
{{ field.value ? df.format(field.value) : "Pick a date" }}
|
||||||
</PopoverTrigger>
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<!-- Tooltip bubble -->
|
||||||
|
<div v-if="!values?.type" class="pointer-events-none absolute -top-9 left-1/2 -translate-x-1/2
|
||||||
|
whitespace-nowrap rounded-md bg-popover px-2 py-1 text-xs
|
||||||
|
text-popover-foreground shadow-md border border-border
|
||||||
|
opacity-0 translate-y-1
|
||||||
|
group-hover:opacity-100 group-hover:translate-y-0
|
||||||
|
transition-opacity transition-transform duration-150">
|
||||||
|
Select an LOA type first
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<PopoverContent class="w-auto p-0">
|
<PopoverContent class="w-auto p-0">
|
||||||
<Calendar
|
<Calendar
|
||||||
:model-value="field.value ? new CalendarDate(field.value.getFullYear(), field.value.getMonth() + 1, field.value.getDate()) : null"
|
|
||||||
@update:model-value="(val: CalendarDate) => field.onChange(val.toDate(getLocalTimeZone()))"
|
@update:model-value="(val: CalendarDate) => field.onChange(val.toDate(getLocalTimeZone()))"
|
||||||
:default-placeholder="defaultPlaceholder" :min-value="minEndDate"
|
:default-placeholder="defaultPlaceholder" :min-value="minEndDate"
|
||||||
:max-value="maxEndDate" layout="month-and-year">
|
:max-value="maxEndDate" layout="month-and-year">
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ import { Plus, X } from 'lucide-vue-next';
|
|||||||
import Separator from '@/components/ui/separator/Separator.vue';
|
import Separator from '@/components/ui/separator/Separator.vue';
|
||||||
import Input from '@/components/ui/input/Input.vue';
|
import Input from '@/components/ui/input/Input.vue';
|
||||||
import Label from '@/components/ui/label/Label.vue';
|
import Label from '@/components/ui/label/Label.vue';
|
||||||
import { getMembers, Member } from '@/api/member';
|
import { getMembers } from '@/api/member';
|
||||||
|
import { Member } from '@shared/types/member';
|
||||||
|
|
||||||
const roles = ref<Role[]>([])
|
const roles = ref<Role[]>([])
|
||||||
const activeRole = ref<Role | null>(null)
|
const activeRole = ref<Role | null>(null)
|
||||||
@@ -116,16 +117,18 @@ async function handleCreateGroup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAddMember() {
|
async function handleAddMember() {
|
||||||
//guard
|
//guard
|
||||||
if (memberToAdd.value == null)
|
if (memberToAdd.value == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
addMemberToRole(memberToAdd.value.member_id, activeRole.value.id);
|
await addMemberToRole(memberToAdd.value.member_id, activeRole.value.id);
|
||||||
|
roles.value = await getRoles();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRemoveMember(memberId: number) {
|
async function handleRemoveMember(memberId: number) {
|
||||||
removeMemberFromRole(memberId, activeRole.value.id);
|
removeMemberFromRole(memberId, activeRole.value.id);
|
||||||
|
roles.value = await getRoles();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDeleteRole() {
|
async function handleDeleteRole() {
|
||||||
@@ -193,7 +196,7 @@ onMounted(async () => {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button @click="handleDeleteRole">Delete Group</Button>
|
<!-- <Button @click="handleDeleteRole">Delete Group</Button> -->
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@@ -232,7 +235,7 @@ onMounted(async () => {
|
|||||||
<div class="max-w-5xl mx-auto">
|
<div class="max-w-5xl mx-auto">
|
||||||
<div class="flex items-center justify-between my-4">
|
<div class="flex items-center justify-between my-4">
|
||||||
<p>Groups</p>
|
<p>Groups</p>
|
||||||
<Button @click="showCreateGroupDialog = true">+ Add New Group</Button>
|
<!-- <Button @click="showCreateGroupDialog = true">+ Add New Group</Button> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 gap-5">
|
<div class="grid grid-cols-3 gap-5">
|
||||||
<Card v-for="value in roles" :key="value.id" @click="activeRole = value; showDialog = true"
|
<Card v-for="value in roles" :key="value.id" @click="activeRole = value; showDialog = true"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { useMemberDirectory } from "@/stores/memberDirectory";
|
|||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
|
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
|
const saveSuccess = ref(false);
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const showLoading = ref(false);
|
const showLoading = ref(false);
|
||||||
const form = ref<memberSettings>();
|
const form = ref<memberSettings>();
|
||||||
@@ -20,13 +21,14 @@ const userStore = useUserStore()
|
|||||||
|
|
||||||
function saveSettings() {
|
function saveSettings() {
|
||||||
saving.value = true;
|
saving.value = true;
|
||||||
|
saveSuccess.value = false;
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
// Replace with your API save call
|
// Replace with your API save call
|
||||||
setMemberSettings(form.value);
|
setMemberSettings(form.value);
|
||||||
saving.value = false;
|
saving.value = false;
|
||||||
console.log(userStore.user.id)
|
memberDictionary.invalidateMember(userStore.user.member.member_id)
|
||||||
memberDictionary.invalidateMember(userStore.user.id)
|
saveSuccess.value = true;
|
||||||
}, 800);
|
}, 800);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +77,8 @@ onMounted(async () => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<CardFooter class="flex justify-end">
|
<CardFooter class="flex justify-end gap-5 items-center">
|
||||||
|
<p v-if="saveSuccess" class="text-green-500">Profile saved</p>
|
||||||
<Button @click="saveSettings" :disabled="saving">
|
<Button @click="saveSettings" :disabled="saving">
|
||||||
{{ saving ? "Saving..." : "Save Changes" }}
|
{{ saving ? "Saving..." : "Save Changes" }}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const router = createRouter({
|
|||||||
{ path: '/members', component: () => import('@/pages/memberList.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
{ path: '/members', component: () => import('@/pages/memberList.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
||||||
{ path: '/loa', component: () => import('@/pages/SubmitLOA.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
{ path: '/loa', component: () => import('@/pages/SubmitLOA.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
||||||
{ path: '/transfer', component: () => import('@/pages/Transfer.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
{ path: '/transfer', component: () => import('@/pages/Transfer.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
||||||
{ path: '/profile', component: () => import('@/pages/MyProfile.vue'), meta: { requiresAuth: true, memberOnly: true } },
|
{ path: '/profile', component: () => import('@/pages/MyProfile.vue'), meta: { requiresAuth: true } },
|
||||||
|
|
||||||
|
|
||||||
{ path: '/calendar', component: () => import('@/pages/Calendar.vue') },
|
{ path: '/calendar', component: () => import('@/pages/Calendar.vue') },
|
||||||
|
|||||||
Reference in New Issue
Block a user