diff --git a/components/SubmitMissingSummary.tsx b/components/SubmitMissingSummary.tsx new file mode 100644 index 0000000..3434d4e --- /dev/null +++ b/components/SubmitMissingSummary.tsx @@ -0,0 +1,117 @@ +import { useState } from 'react'; +import styles from '../styles/SubmitMissingSummary.module.css'; +import { saveMissingSummary } from '../utils/saveMissingSummaries'; + +function formatDate(dateString: any) { + const options = { year: 'numeric', month: 'long', day: 'numeric' }; + const date = new Date(dateString); + // Manually construct the date string to ensure it's in "DD MMMM YYYY" format + const formattedDate = `${date.getDate()} ${date.toLocaleString('en-US', { month: 'long' })} ${date.getFullYear()}`; + return formattedDate; + } + +const SubmitMissingSummary = ({ workgroups, allSummaries }: any) => { + const [selectedWorkgroup, setSelectedWorkgroup] = useState<{ workgroup: string; workgroup_id: string; } | null>(null); + const [meetingDate, setMeetingDate] = useState(''); + + //console.log("allSummaries", allSummaries) + const handleSubmit = async (e: any) => { + e.preventDefault(); + if (selectedWorkgroup && meetingDate) { + const missingSummaryData = { + workgroup: selectedWorkgroup.workgroup, + workgroupId: selectedWorkgroup.workgroup_id, + meetingDate: formatDate(meetingDate), // Assuming you want the formatted date here + status: "Missing", + type: "weekly" + }; + const newRow = { + meetingDate: formatDate(meetingDate), + workgroup: selectedWorkgroup.workgroup, + status: "Missing" + }; + + const conflictSummary = allSummaries.find((summary: any) => + summary.meetingDate === newRow.meetingDate && summary.workgroup === newRow.workgroup + ); + + // If a "Done" summary exists for the same meetingDate and workgroup, alert the user + if (conflictSummary && conflictSummary.status === "Done") { + alert("This meeting is already marked as Done."); + return; // Prevent the submission + } + + const summariesToUpdate = newRow ? [...allSummaries, newRow] : allSummaries; + + try { + // First, save the missing summary through your existing utility function + const saveResponse = await saveMissingSummary(missingSummaryData); + console.log('Save missing summary response:', saveResponse); + + // If the save operation is successful, then update the CSV in GitHub + const response = await fetch('/api/updateCSV', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + owner: "SingularityNET-Archive", + repo: "SingularityNET-Archive", + path: "Data/status-of-summaries.csv", + summariesToUpdate + }), + }); + + if (!response.ok) { + throw new Error('Failed to update CSV'); + } + + const responseData = await response.json(); + console.log('CSV updated successfully:', responseData); + + // Reset form state here + setSelectedWorkgroup(null); + setMeetingDate(''); + } catch (error) { + console.error('Error:', error); + // Handle error, e.g., showing an error message + } + } + }; + + return ( +
+
+ + +
+
+ + setMeetingDate(e.target.value)} + required + /> +
+ +
+ ); +}; + +export default SubmitMissingSummary; diff --git a/components/nav.tsx b/components/nav.tsx index 256784d..ec3b46c 100644 --- a/components/nav.tsx +++ b/components/nav.tsx @@ -110,7 +110,6 @@ const Nav = () => { }); }) .catch(error => console.error('Error:', error)); - }, [session]); async function saveUsername() { @@ -129,6 +128,9 @@ const Nav = () => { Issues + {roleData?.appRole == "admin" && ( + Summaries + )}
{latestTag}
diff --git a/pages/api/fetchCsv.js b/pages/api/fetchCsv.js new file mode 100644 index 0000000..d98e5d3 --- /dev/null +++ b/pages/api/fetchCsv.js @@ -0,0 +1,40 @@ +// pages/api/fetchCsv.js + +export default async function handler(req, res) { + const csvUrl = 'https://raw.githubusercontent.com/SingularityNET-Archive/SingularityNET-Archive/main/Data/status-of-summaries.csv'; + + try { + const csvResponse = await fetch(csvUrl); + const csvText = await csvResponse.text(); + + // Convert CSV text to JSON + const json = csvToJson(csvText); // Implement this function based on your CSV structure + + res.status(200).json(json); + } catch (error) { + res.status(500).json({ error: "Failed to fetch CSV data" }); + } +} + +function csvToJson(csv) { + const lines = csv.split('\n'); + const result = []; + const headers = lines[0].split(','); + + for (let i = 1; i < lines.length; i++) { + const currentline = lines[i].split(','); + // Check if the line is empty or consists of only whitespace + if (currentline.length === 1 && currentline[0].trim() === '') { + continue; // Skip this iteration, effectively ignoring the empty row + } + + let obj = {}; + for (let j = 0; j < headers.length; j++) { + obj[headers[j]] = currentline[j]; + } + + result.push(obj); + } + + return result; +} diff --git a/pages/api/issues.js b/pages/api/issues.js index 47a5e1e..02ab06e 100644 --- a/pages/api/issues.js +++ b/pages/api/issues.js @@ -6,14 +6,16 @@ export default async function handler(req, res) { const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); try { - const response = await octokit.rest.issues.listForRepo({ + const issues = await octokit.paginate(octokit.rest.issues.listForRepo, { owner: 'SingularityNET-Archive', repo: 'archive-oracle', state: 'all', + per_page: 100, // Adjust per_page to your needs }); - res.status(200).json(response.data); + res.status(200).json(issues); } catch (error) { + console.error("Failed to fetch issues:", error); res.status(500).json({ error: error.message }); } } diff --git a/pages/api/updateCSV.js b/pages/api/updateCSV.js new file mode 100644 index 0000000..ed18c8e --- /dev/null +++ b/pages/api/updateCSV.js @@ -0,0 +1,24 @@ +// File: pages/api/updateCSV.js +import { fetchAndUpdateCSV } from '../../utils/updateCSVInGitHub'; + +export default async function handler(req, res) { + if (req.method !== 'POST') { + return res.status(405).send({ message: 'Only POST requests allowed' }); + } + + const { owner, repo, path, branch, summariesToUpdate } = req.body; + + try { + const updateResponse = await fetchAndUpdateCSV({ + owner, + repo, + path, + branch, + summariesToUpdate + }); + res.status(200).json(updateResponse); + } catch (error) { + console.error('Error updating CSV in GitHub:', error); + res.status(500).json({ message: 'Failed to update CSV', error: error.message }); + } +} diff --git a/pages/status-of-summaries.tsx b/pages/status-of-summaries.tsx new file mode 100644 index 0000000..bc37b0b --- /dev/null +++ b/pages/status-of-summaries.tsx @@ -0,0 +1,112 @@ +import { useState, useEffect } from "react"; +import type { NextPage } from "next"; +import styles from '../styles/summariestable.module.css'; +import SubmitMissingSummary from '../components/SubmitMissingSummary'; +import { getWorkgroups } from '../utils/getWorkgroups'; +import { getMissingSummaries } from '../utils/getMissingSummaries'; + +interface SummaryData { + meetingDate: string; + status: string; + workgroup: string; + workgroupId: string; +} + +const StatusOfSummaries: NextPage = () => { + const [loading, setLoading] = useState(false); + const [data, setData] = useState([]); + const [workgroups, setWorkgroups] = useState([]); + const [allSummaries, setAllSummaries] = useState([]); + + async function fetchCsvData() { + setLoading(true); + try { + const response = await fetch('/api/fetchCsv'); + const data = await response.json(); + console.log('Fetched CSV data:', data); + setData(data); + } catch (error) { + console.error("Failed to fetch CSV data:", error); + } + setLoading(false); + } + + useEffect(() => { + //fetchCsvData(); + fetchWorkgroups(); // Add this line + }, []); + + async function fetchWorkgroups() { + try { + const databaseWorkgroups = await getWorkgroups(); + const allSummaries = await getMissingSummaries(); + setAllSummaries(allSummaries) + + // Assuming databaseWorkgroups should be an array, check and handle accordingly + if (Array.isArray(databaseWorkgroups)) { + setWorkgroups(databaseWorkgroups); + } else { + console.error("Expected an array for workgroups, received:", databaseWorkgroups); + // Handle the unexpected format, e.g., set to an empty array or a default value + setWorkgroups([]); + } + } catch (error) { + console.error("Error fetching workgroups:", error); + setWorkgroups([]); // Handle error by setting workgroups to an empty array or another default state + } + } + + return ( +
+ +

Status of Summaries

+ {loading &&

Loading...

} + +
+ {/* Missing Summaries Table */} +

Missing Summaries

+ + + + + + + + + + {allSummaries.filter(row => row.status === "Missing").map((row, index) => ( + + + + + + ))} + +
Meeting DateWorkgroupStatus
{row.meetingDate}{row.workgroup}{row.status}
+ + {/* Done Summaries Table */} +

Done Summaries

+ + + + + + + + + + {allSummaries.filter(row => row.status === "Done").map((row, index) => ( + + + + + + ))} + +
Meeting DateWorkgroupStatus
{row.meetingDate}{row.workgroup}{row.status}
+
+
+ ); +}; + +export default StatusOfSummaries; diff --git a/pages/submit-meeting-summary/index.tsx b/pages/submit-meeting-summary/index.tsx index 266f453..af9b8da 100644 --- a/pages/submit-meeting-summary/index.tsx +++ b/pages/submit-meeting-summary/index.tsx @@ -220,6 +220,17 @@ useEffect(() => { }); }; + const resetSummary = () => { + setMyVariable(prevMyVariable => ({ + ...prevMyVariable, + summary: { + ...prevMyVariable.summary, + meetingInfo: {}, + agendaItems: [], + tags: {} + } + })); + }; return (
@@ -277,6 +288,12 @@ useEffect(() => { {selectedWorkgroupId && (<> {myVariable.roles?.isAdmin && ()} {myVariable.roles?.isAdmin && } + )}
{myVariable.isLoggedIn && selectedWorkgroupId && (
diff --git a/styles/SubmitMissingSummary.module.css b/styles/SubmitMissingSummary.module.css new file mode 100644 index 0000000..e321e63 --- /dev/null +++ b/styles/SubmitMissingSummary.module.css @@ -0,0 +1,20 @@ +.container { + margin-top: 50px; + margin-bottom: 20px; +} + +.formGroup { + margin-bottom: 10px; +} + +.submitButton { + background-color: #4CAF50; + color: white; + padding: 10px 20px; + border: none; + cursor: pointer; +} + +.submitButton:hover { + background-color: #45a049; +} diff --git a/styles/meetingsummary.module.css b/styles/meetingsummary.module.css index f42d277..2cb084e 100644 --- a/styles/meetingsummary.module.css +++ b/styles/meetingsummary.module.css @@ -95,6 +95,24 @@ font-size: 1rem; } +.resetButton { + width: 170px; + display: block; + padding: 0.5rem; + margin: 0.5rem 0; + text-align: center; + background-color: rgb(18, 105, 198); + border: none; + cursor: pointer; + color: white; + z-index: 9999; + font-size: 1rem; +} +.resetButton:hover { + background-color: rgb(13, 64, 120); + outline: 4px auto -webkit-focus-ring-color; +} + .navButton:focus { outline: 4px auto -webkit-focus-ring-color; } diff --git a/styles/summariestable.module.css b/styles/summariestable.module.css new file mode 100644 index 0000000..af397d5 --- /dev/null +++ b/styles/summariestable.module.css @@ -0,0 +1,65 @@ +.container { + color: #fff; /* White text color for better readability */ + background-color: black; /* Assuming your background is black */ + padding: 20px; +} + +.issuesTable { + width: 100%; + border-collapse: collapse; + margin-bottom: 20px; + table-layout: fixed; /* Add this line */ +} + +.tableHeader, .tableData { + text-align: left; + padding: 8px; + border-bottom: 1px solid #ddd; /* Light grey border for some contrast */ +} + +.tableHeader { + background-color: #333; /* Darker background for headers */ + color: #fff; +} + +/* Specify column widths */ +.tableHeader:nth-child(1) { + width: 30%; /* Adjust based on your preference */ +} + +.tableHeader:nth-child(2) { + width: 50%; /* Adjust based on your preference */ +} + +.tableHeader:nth-child(3) { + width: 20%; /* Adjust based on your preference */ +} + +.tableRow:hover { + background-color: #555; /* Slightly lighter row background on hover */ +} + +.missingHeading { + color: rgb(194, 129, 254); /* Golden color for headings */ +} + +.doneHeading { + color: rgb(0, 255, 221); /* Golden color for headings */ +} + +.createIssueButton { + background-color: #4CAF50; /* Green background */ + color: white; + padding: 10px 20px; + margin: 10px 0; + border: none; + cursor: pointer; +} + +.createIssueButton:hover { + background-color: #45a049; +} + +.issuesTableContainer { + overflow-x: auto; +} diff --git a/utils/getMissingSummaries.js b/utils/getMissingSummaries.js new file mode 100644 index 0000000..77497fc --- /dev/null +++ b/utils/getMissingSummaries.js @@ -0,0 +1,78 @@ +import { supabase } from "../lib/supabaseClient"; + +function formatDate(dateString) { + const options = { year: 'numeric', month: 'long', day: 'numeric' }; + const date = new Date(dateString); + // Manually construct the date string to ensure it's in "DD MMMM YYYY" format + const formattedDate = `${date.getDate()} ${date.toLocaleString('en-US', { month: 'long' })} ${date.getFullYear()}`; + return formattedDate; +} + +export async function getMissingSummaries() { + let missingSummaries = []; + let allDoneSummaries = []; + + async function getAllMissingSummaries() { + try { + const { data, error, status } = await supabase + .from('missingsummaries') + .select('workgroup, meeting_date, status'); + + if (error && status !== 406) throw error; + if (data) { + missingSummaries = data.map(item => ({ + ...item, + meetingDate: item.meeting_date, + status: item.status, + workgroup: item.workgroup + })); + } + } catch (error) { + console.log("error", error.message); + } + } + + async function getAllSummaries() { + try { + const { data, error, status } = await supabase + .from('meetingsummaries') + .select('date, summary') + .order('date', { ascending: false }); + + if (error && status !== 406) throw error; + if (data) { + allDoneSummaries = data.map(item => ({ + meetingDate: formatDate(item.date), + status: "Done", // Setting status to "Done" for all done summaries + workgroup: item.summary.workgroup + })); + } + } catch (error) { + console.log("error", error.message); + } + } + + await getAllMissingSummaries(); + await getAllSummaries(); + + const combinedSummaries = [...missingSummaries, ...allDoneSummaries]; + const uniqueSummariesMap = new Map(); + + combinedSummaries.forEach(summary => { + const key = `${summary.meetingDate}-${summary.workgroup}`; + // Check if the map already has an entry for this key + if (uniqueSummariesMap.has(key)) { + // Prioritize "Done" summaries over "Missing" + const existingSummary = uniqueSummariesMap.get(key); + if (existingSummary.status !== "Done" && summary.status === "Done") { + uniqueSummariesMap.set(key, summary); + } + } else { + uniqueSummariesMap.set(key, summary); + } + }); + + const finalSummaries = Array.from(uniqueSummariesMap.values()); + + return finalSummaries; + } \ No newline at end of file diff --git a/utils/getsummaries.js b/utils/getsummaries.js index 5d9f7b7..a21266e 100644 --- a/utils/getsummaries.js +++ b/utils/getsummaries.js @@ -49,9 +49,9 @@ export async function getSummaries(workgroup_id) { // Fetch the user details for each summary for (const data of summaryData) { // Exclude meetings where confirmed == true and all meetings with an earlier date and updated_at date than the last meeting where confirmed == true - if (data.confirmed === true || new Date(data.date) < new Date(lastConfirmedDate)) { //Might have to add this back in later - || new Date(data.updated_at) < new Date(lastUpdatedAt) + /*if (data.confirmed === true || new Date(data.date) < new Date(lastConfirmedDate)) { //Might have to add this back in later - || new Date(data.updated_at) < new Date(lastUpdatedAt) continue; - } + }*/ const { data: userData, error: userError } = await supabase .from('users') diff --git a/utils/saveMissingSummaries.js b/utils/saveMissingSummaries.js new file mode 100644 index 0000000..2a8aaef --- /dev/null +++ b/utils/saveMissingSummaries.js @@ -0,0 +1,23 @@ +import { supabase } from "../lib/supabaseClient"; + +export async function saveMissingSummary(summaryData) { + let updates = { + workgroup_id: summaryData.workgroupId, + workgroup: summaryData.workgroup, + meeting_date: summaryData.meetingDate, + status: summaryData.status, + type: summaryData.type + } + + const { data, error } = await supabase + .from('missingsummaries') + .upsert(updates, { onConflict: ['workgroup_id', 'meeting_date', 'type'] }) + .select('meeting_date, workgroup, updated_at'); + + if (error) { + console.error('Error upserting data:', error); + return false; + } + + return data; +} diff --git a/utils/updateCSVInGitHub.js b/utils/updateCSVInGitHub.js new file mode 100644 index 0000000..08b034f --- /dev/null +++ b/utils/updateCSVInGitHub.js @@ -0,0 +1,89 @@ +// File: utils/githubCSVUtils.js +import { Octokit } from "@octokit/rest"; + +const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); + +export const fetchAndUpdateCSV = async ({ owner, repo, path, branch = 'main', summariesToUpdate }) => { + // Fetch the current CSV content + const { data: fileData } = await octokit.repos.getContent({ + owner, + repo, + path, + ref: branch, + }); + const currentCSV = Buffer.from(fileData.content, 'base64').toString('utf8'); + + // Update the CSV content based on allSummaries + const updatedCSV = updateCSVContent(currentCSV, summariesToUpdate); + + // Encode the updated CSV content in Base64 for GitHub + const contentEncoded = Buffer.from(updatedCSV).toString('base64'); + + // Update the file on GitHub + const { data: updateResponse } = await octokit.repos.createOrUpdateFileContents({ + owner, + repo, + path, + message: "Update CSV with new summaries", + content: contentEncoded, + sha: fileData.sha, + branch, + }); + + return updateResponse; +}; + +const updateCSVContent = (currentCSV, summariesToUpdate) => { + const lines = currentCSV.split('\n').filter(line => line.trim()); // Exclude empty lines + const headers = lines.shift().split(','); // Extract headers + let csvObjects = lines.map(line => line.split(',').reduce((acc, cur, i) => ({ + ...acc, + [headers[i]]: cur, + }), {})); + + // Create a map of existing summaries for quick lookup + let existingSummariesMap = new Map(csvObjects.map(obj => [`${obj.meetingDate}-${obj.workgroup}`, obj])); + + // Iterate over allSummaries to update existing or add new entries + summariesToUpdate.forEach(summary => { + const key = `${summary.meetingDate}-${summary.workgroup}`; + if (existingSummariesMap.has(key)) { + // Update existing entry + const existingEntry = existingSummariesMap.get(key); + existingEntry.status = summary.status; // Assuming status is the field to update + } else { + // Add new entry + existingSummariesMap.set(key, summary); + } + }); + + // Convert the updated map back to CSV string + const updatedCSVObjects = Array.from(existingSummariesMap.values()); + // Function to parse custom date format into a Date object + function parseCustomDate(dateString) { + const [day, month, year] = dateString.split(' '); + const monthIndex = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"].indexOf(month); + return new Date(year, monthIndex, day); + } + + // Sort the objects by status ("Missing" at the top), then by date in descending order + updatedCSVObjects.sort((a, b) => { + // Sort by status + if (a.status === "Missing" && b.status !== "Missing") { + return -1; + } else if (a.status !== "Missing" && b.status === "Missing") { + return 1; + } + + // Then sort by date within the same status group + const dateA = parseCustomDate(a.meetingDate); + const dateB = parseCustomDate(b.meetingDate); + return dateB - dateA; // Descending order + }); + + // Convert sorted objects back to CSV lines + const updatedCSVLines = updatedCSVObjects.map(obj => headers.map(header => obj[header] || "").join(',')); + const updatedCSV = [headers.join(',')].concat(updatedCSVLines).join('\n'); + + return updatedCSV; +}; \ No newline at end of file