What does `**/[^_]*.md^` in Astro glob() Match?

In The docs for the new Astro 5.0.0 release, the new syntax for content collections is introduced docs are here

In there, I see a glob pattern **/[^_]*.md but not how this should differ from ***.md. Can anyone help out here? Globs are not regexes so I don’t think these are filenames that start with a _or ^ or do not start with an underscore. Does this indicate it looks for references to similar files?

React select shows extra element that is not in the array assigned to value

I am learning how to use components in React. I have a parent called Quote.jsx that has a child called QuoteData.jsx. I send the value of selectedCharacters from the parent to the child. On the child I am using a react-select control. The react-select control’s value is set to selectedCharacters.

I know selectedCharacters only has 2 elements in it because I can see that on the screen via the temporary output message I put in there that checks the length of selectedCharacters and shows the values in those 2 elements. But the react-select element shows an extra blank element and I cannot figure out why.

Since a picture’s worth a thousand, this is what I mean
enter image description here

And it’s not only a single element. I mean it is when I first load the page but as I use the navigation buttons, extra blank items are showing in the react-select.

My jsx files look like this:

Quote.jsx

import React, { useState, useEffect } from 'react';
//import Select from 'react-select';
import axios from 'axios';
import { ToastContainer, toast } from 'react-toastify';
import 'bootstrap/dist/css/bootstrap.min.css'; 
import 'react-toastify/dist/ReactToastify.css';
import './App.css';
import Processing from './Processing';
import QuoteData from './QuoteData';

function Quote() {
  const [quote, setQuote] = useState({ Text: '', Id: 0, Formatted: '', IsActive: true,  Characters: [] }); 
  const [allCharacters, setAllCharacters] = useState([]);
  const [selectedCharacters, setSelectedCharacters] = useState([]);
  const [allQuoteIds, setAllQuoteIds] = useState([]);
  const [currentQuoteIdIndex, setCurrentQuoteIdIndex] = useState(0);
  const [selectedOption, setSelectedOption] = useState('optAll');
  const [statusCount, setStatusCount] = useState({formatted_count: 0, non_formatted_count: 0, total_count: 0});
  const [loadingIndicator, setLoadingIndicator] = useState(true)
  

  // ---------------------- HOOKS ---------------------- 
  useEffect(() => {
    try {
      getAllQuoteIds();
      getCharacterData();
      getStatusCount();
    } catch (error) {
      showToast('error', 'Error gathering initial data.  Error reported is ' + error.message)
    }
  }, []);

  useEffect(() => {
    try {
      getQuoteData();
      setSelectedCharacters(quote.Characters)
    } catch (error) {
      showToast('error', 'Problem getting/setting quote data' + error.message)
    }
  }, [allQuoteIds, currentQuoteIdIndex]);


  // ---------------------- HELPERS ---------------------- 
  const showToast = (type, message) => {
    const toastOptions = {
      position: "top-center",
      autoClose: 500,
      hideProgressBar: false,
      closeOnClick: true,
      draggable: true,
      progress:undefined,
    }

    if (type === "warning") {
      toast.warning(message, {toastOptions});
    } else if (type === "success") {
      toast.success(message, {toastOptions});
    } else if (type === "error") {
      toast.error(message, {toastOptions})
    } else if (type === "info") {
      toast.info(message, {toastOptions})
    } else {
      toast.error('Unknown issue occurred when trying to display status', {toastOptions})
    }
  }

  const getCharacterData = async () => {
    try {
      loadCharacters(false);
    } catch (error) {
      showToast('error', 'Problem getting/loading character data.  Problem reported: ' + error.message);
    }
  };


  // ----------------------  GET CALLS ---------------------- 
  const getQuoteData = async () => {
    if(allQuoteIds[currentQuoteIdIndex] === undefined) {
      // do nothing
    } else {
      try {
        let url = 'http://127.0.0.1:5000/AdminAPI/getOneQuote/' + allQuoteIds[currentQuoteIdIndex]
        const response = await axios.get(
          url,
          {
            headers: {
              'Access-Control-Allow-Origin': '*',
              'Content-Type': 'application/json',
            },
          }
        );
        if (response.status === 200) { 
          setLoadingIndicator(false);
          setQuote(response.data.quotes[0]);
          setSelectedCharacters(response.data.quotes[0].Characters.map((character) => ({
            value: character.Id,
            label: character.ShortName,
          })));
        } else {
          showToast('error', 'Problem getting quote data');
        }
      } catch (error) {
        showToast('error', 'Problem getting quote data.  Problem reported: ' + error.message);
      }
    }
  };

 
  const getAllQuoteIds = async (whatToShow = 'optAll') => {
    try {
      let urlBase = 'http://127.0.0.1:5000/AdminAPI/getAllQuoteIds';
      let url = '';
      
      if (whatToShow === "optFormatted") {
        url = urlBase + '?formatted=true';
      }
      else if (whatToShow === "optNonFormatted") {
        url = urlBase + '?formatted=false';
      } 
      else {
        url = urlBase;
      }
  
      const response = await axios.get(
        url, 
        {
          headers: {
            'Access-Control-Allow-Origin': '*',
            'Content-Type': 'application/json',
          },
        }
      );
      setAllQuoteIds(response.data);
    } catch (error) {
      showToast('error', 'Problem getting quoteID data.  Problem reported: ' + error.message);
    }
  }

  const getStatusCount = async() => {
    try {
      const response = await axios.get(
        'http://127.0.0.1:5000/AdminAPI/GetQuoteFormattingStatusCount', 
        {
          headers: {
            'Access-Control-Allow-Origin': '*',
            'Content-Type': 'application/json',
          },
        }
      );
      setStatusCount(response.data)
    } catch (error) {
      showToast('error', 'Problem getting status count data.  Problem reported: ' + error.message);
    }
  }

  // ----------------------  POST CALLS ----------------------  

  const saveQuote = async () => {
    try {
      const quoteData = {
        Id: quote.Id, 
        Text: quote.Text,
        Characters: selectedCharacters.map((character) => character.value), 
        Formatted: true, 
        IsActive: quote.IsActive
      };
  
      try {
        const response = await axios.post('http://127.0.0.1:5000/AdminAPI/saveQuote', quoteData, {
          headers: {
            'Content-Type': 'application/json',
          },
        });
    
        // Handle successful response
        if (response.status === 200) {
          showToast("success", "Quote saved successfully!")
          getStatusCount();
          handleNavigationChange({ target: {value: "next"}});
        } else {
          showToast("error", "Error saving quote")
  //        toast.error('Error saving quote');
          //TODO: Put in logging
        }
      } catch (error) {
        showToast("error", "Error saving quote")
  //      toast.error('Error saving quote');
        //TODO: Put in logging
        }
      } catch (error) {
        showToast('error', 'Problem saving quote data.  Problem reported: ' + error.message);
      }
  };


  // ----------------------  EVENT HANDLING ----------------------  
  const handleNavigationChange = (event) => {

    if (event.target.value === "start") {
      setCurrentQuoteIdIndex(0); 
    } else if (event.target.value === "prev"){
      if (currentQuoteIdIndex === 0){
        showToast("warning", "Cannot move backward any further")
      } else {
        setCurrentQuoteIdIndex(currentQuoteIdIndex-1);
      }
    } else if (event.target.value === "next"){
      if (currentQuoteIdIndex >= allQuoteIds.length-1) {
            showToast("warning", "Cannot move forward any further")
        } else {
            setCurrentQuoteIdIndex(currentQuoteIdIndex+1);
        }
    } else {
      setCurrentQuoteIdIndex(allQuoteIds.length-1)
    }
  }

  const handleQuoteChange = (event) => {
    setQuote({ ...quote, Text: event.target.value }); 
  };

  const handleCharChange = (selected) => {
    setSelectedCharacters(selected);
    setQuote({...quote, Characters: selected });
  };

  const handleOptionChange = (event) => {
    setLoadingIndicator(true);
    setSelectedOption(event.target.value);
    getAllQuoteIds(event.target.value);
  };
  
  const handleIsActiveChange = (event) => {
    setQuote({ ...quote, IsActive: event.target.checked }); 
  }
 
  const handleClickReloadCharacters = (event) => {
    loadCharacters(true);
  };

  const loadCharacters = async (showSuccessMessage) => {
    try {
      const response = await axios.get(
        'http://127.0.0.1:5000/AdminAPI/getAllCharacters',
        {
          headers: {
            'Access-Control-Allow-Origin': '*',
            'Content-Type': 'application/json',
          },
        }
      );
  
      const formattedCharacters = response.data.characters.map((character) => ({
          value: character.Id,
          label: character.ShortName,
        }));
      setAllCharacters(formattedCharacters);
      if (showSuccessMessage) {
        showToast('info', 'Characters loaded')
      }
    } catch (error) {
      showToast('error', 'Problem getting/loading character data.  Problem reported: ' + error.message);
    }
  }      


  // ----------------------  PAGE ----------------------  
  return (
    <div>
      <ToastContainer/>
      <br/>
      <div className='container'>
        <div className="row">
          <div className="col-md-12">
            <select value={selectedOption} onChange={handleOptionChange}>
              <option value="optAll">Show All Quotes</option>
              <option value="optFormatted">Only Show Formatted Quotes</option>
              <option value="optNonFormatted">Only Show Non-Formatted Quotes</option>
            </select>
          </div>
        </div>
      </div>
      <div style={{height: "10%"}}>&nbsp;</div>
            {
              loadingIndicator === false ? (
              <QuoteData 
                quote={quote} 
                onCharChange={handleCharChange} 
                onIsActiveChange={handleIsActiveChange}
                onQuoteChange={handleQuoteChange}
                allCharacters={allCharacters}
                selectedCharacters={selectedCharacters}
                >
              </QuoteData>
            ) : (
              <div className="container-fluid d-flex align-items-center justify-content-center"
                style={{padding: "20px", height: "50vh", width: "50vw"}}>
                <Processing></Processing>
              </div>
            )}

        <button className='btn btn-primary' onClick={saveQuote}>Save Quote</button> 
        <br/><br/>
        <button className='btn btn-primary' value="start" onClick={handleNavigationChange}>Start</button>
        &nbsp;
        <button className='btn btn-primary' value="prev" onClick={handleNavigationChange}>Prev</button>
        &nbsp;
        <button className='btn btn-primary' value="next" onClick={handleNavigationChange}>Next</button> 
        &nbsp;
        <button className='btn btn-primary' value="end" onClick={handleNavigationChange}>End</button>
        <br/><br/>
        <button className='btn btn-primary' value="reloadCharacters" onClick={handleClickReloadCharacters}>Reload Characters</button>
        <br/><br/>
        <div className="container-fluid d-flex align-items-center justify-content-center">
          Formatted: {statusCount.formatted_count} of {statusCount.total_count} &nbsp;&nbsp;&nbsp;
        <div className="progress" style={{height: "40px", width: "50%"}} >
          <div className="progress-bar bg-success" style={{width: ((statusCount.formatted_count/statusCount.total_count)*100).toString() + "%"}} >
            { Math.fround((statusCount.formatted_count/statusCount.total_count)*100).toFixed(2)}%
          </div>
          <div className="progress-bar bg-danger" style={{width: ((statusCount.non_formatted_count/statusCount.total_count)*100).toString() + "%"}} >
            { Math.fround((statusCount.non_formatted_count/statusCount.total_count)*100).toFixed(2)}%
          </div>
        </div>
        </div>
    </div>
  );
}

export default Quote;

QuoteData.jsx

import 'bootstrap/dist/css/bootstrap.min.css'; 
import './App.css';
import Select from 'react-select';

function QuoteData ({quote, allCharacters, selectedCharacters, onIsActiveChange, onQuoteChange, onCharChange}) {

      const handleCharChange = (selected) => {
        const formattedSelected = selected || [];
        onCharChange(formattedSelected);
      };


      return (
        <div>
        <div className='container'> 
        <div className='row'>
            <div className='col text-start'>
            {quote.Book.Title}
            </div>
            <div className="col text-end">
            Quote ID: {quote.Id}
            <text value={quote.Id} type="hidden"></text>
            </div>
        </div>
        <div className='row'>
            <div className='col-lg'>
            <textarea className="form-control border border-primary" 
                style={{ fontSize: '18px'}}
                value={quote.Text} rows="17" 
                onChange={onQuoteChange}
                disabled={!quote.IsActive}
            />

            {/* For testing only */}
            SelectedCharacters length is {selectedCharacters.length}
            <br></br>
            {selectedCharacters && selectedCharacters.length > 0 ? (
            <div>
                <ul>
                {selectedCharacters.map((character) => (
                    <li key={character.value}>{character.label}</li> // Display ShortName in a list item
                ))}
                </ul>
            </div>
            ) : (
            <div>No characters selected</div>
            )}
            {/* end for testing */}
            <div>
            <Select
                isMulti
                options = {
                    allCharacters
                }
                value={selectedCharacters}
                onChange={handleCharChange}
            >
            </Select>
            </div>
            <input type="checkbox" id="chkIsActive" checked={quote.IsActive} onChange={onIsActiveChange} />
            <label htmlFor="chkIsActive"> Is Active</label>
            </div>
        </div>
        </div>
        </div>
    )
}

export default QuoteData

I would greatly appreciate it if you could help me understand where this (these after navigation) extra element(s) are coming from. Thanks!

How to parse HTML hidden behind JS scripts

The FCC has a database with details about various broadcast licenses. Many of these licenses have pages like this one

Most of the data on these pages (and related ones) can be scraped very easily with a combination of the standard requests library and BeautifulSoup4. You just scrape the HTML, target the data you want, and you’re good to go.

I took the same approach to extracting this Spectrum and Market Area table (pictured below), but have run into a roadblock.
Table I want to extract from the page

Though the individual table rows can be inspected with browser dev tools, when I scrape the HTML with something like this:

import requests

url = "https://wireless2.fcc.gov/UlsApp/UlsSearch/leasesList.jsp?licKey=2591153"
output_file = "license_page_leases.html"
response = requests.get(url)
    
with open(output_file, "w", encoding="utf-8") as file:
    file.write(response.text)

… no part of the table itself is downloaded – all I get is the javascript that appears to generate the table.

So my question: how do I scrape the data in this kind of table?

I’ve tried various similar ways of scraping this table.

I’ve also tried to see if theres some underlying query structure available that would let me make a request more directly to their DB, to no success.

If there’s an obvious solution I’m missing, I’d love to know, but I don’t necessarily need anyone to solve this problem for me – I’m here because I need advice on how to research this structure. I don’t know what to call it when the html is generated on the fly like this, so its hard to research methods to grab it.

Thanks!

True reliable message delivery over a Livekit WebRTC data channel using Javascript?

The LiveKit documentation has this note regarding publishing data over the ‘Reliable’ WebRTC data channel:

note

Reliable delivery indicates “best-effort” delivery. It cannot fully guarantee the message will be delivered in all cases. For instance, a receiver that is temporarily disconnected at the moment the message is sent will not receive it. Messages are not buffered on the server and only a limited number of retransmissions are attempted.

Having missing or unordered messages can throw a loop in my application logic. For example: chunked file transfers & implementing higher level handshakes over the data channel.

I’ve naively tried to fix this by incrementing a counter on the sending and receiving peers, and sending that counter along with each message over the data channel. When an incoming message skips ahead of the receiver’s counter, the receiver will queue messages until the missing one(s) come in. That works great assuming messages never get lost! Of course, they do get lost without warning when one peer or the other changes wifi networks or cell towers or reloads the webpage! Using message acknowledgements would work here, but properly implementing ACKs to minimize edge cases and bandwidth congestion is not straightforward.

I know there are many protocols out there that solve this problem and took years to get right like MQTT, ZeroMQ, TCP, & QUIC. However, I couldn’t find a library or protocol that has support for web browsers without a backend over arbitrary transports like Livekit WebRTC data channels.

Q: Does such a Javascript library exist? If not, what is the best approach to implement this and what are the gotchas that this approach avoids or that I need to handle?

Simplifying assumptions I can make:

  1. Each message is only going between two participants with defined roles (there may be more participants in the room but each message exchange is one-on-one. No ‘broadcast’ type messages).
  2. Both participants are using web browsers and the Livekit Client JS SDK.
  3. Both participants were connected to the same Livekit room and are briefly disconnected for a few seconds.
  4. Messages do not need to be persisted and re-sent if one peer or the other closes their web browser or reloads the page
    1. However! This shouldn’t prevent new messages from flowing if the same participant re-joins in a few seconds!
    2. Ideally, I should be able to detect a participant who has permanently left / will never receive messages and update state accordingly.
  5. It seems like ordering of messages is actually guaranteed by Livekit now – I made a Test Code Sandbox to check that.

Other things I’ve thought about:

  1. Do I need to worry about WebRTC buffers and congestion control? – Perhaps the Livekit event DCBufferStatusChanged is useful here.

How to refactor my index.js page so that my db connection isn’t called when running tests

I’m building an API and used a tutorial as a basis, but it didn’t cover testing, so I’m looking too add testing as I’m going. But when I import my app object into the test it’s running all the code (as expected) to get the routes, but my db connection logic is getting called too, so in the tests, the db connection triggers.

How do I refactor my index page so that I can call the app object without having to have the db connection logic called as well.

This is to run the tests locally, as unit tests before I’d push to my repo to ensure I’ve not broken any other endpoints, logic etc.

I’m using Express/JS/Mocha

Here is my index file:

require('./Models/UsersModel')
const express = require('express')
require('dotenv').config()
const mongoose = require('mongoose')
const bodyParser = require('body-parser')
const authRoutes = require('./routes/authRoutes')
const requireAuth = require('../src/middlewares/requireAuth')


const app = express()
app.use(bodyParser.json())
app.use(authRoutes)

const PORT = 3000 || process.env.PORT

const MONGO_URI = process.env.MONGO_URI
mongoose.connect(MONGO_URI, {})
mongoose.connection.on('connected', () => {
    console.log('connected to dev-cluster')
})
mongoose.connection.on('error', (err) => {
    console.log('error connecting to dev-cluster')
    console.log(err)
})


app.get('/', requireAuth, (req, res) => {
    res.send(`${req.user.email}`)
})

app.listen(PORT, () => {
    console.log(`app started on port:${PORT} `)
})

And here is an example test:

const request = require('supertest');
const app = require('../src/index'); 

describe('Authentication Routes', () => {
    it('POST /login should respond with 200 and user data', async () => {
        const response = await request(app)
            .post('/login')
            .send({ username: 'testuser', password: 'testpass' });
        expect(response.statusCode).toBe(200);
        expect(response.body).toHaveProperty('user');
    });

    it('POST /register should respond with 201 and user data', async () => {
        const response = await request(app)
            .post('/register')
            .send({ username: 'newuser', password: 'newpass' });
        expect(response.statusCode).toBe(201);
        expect(response.body).toHaveProperty('user');
    });

    it('GET /logout should respond with 200', async () => {
        const response = await request(app).get('/logout');
        expect(response.statusCode).toBe(200);
    });
});

Any other tips/advice would be appreciated.
Thanks very much!

Convert screenX/Y into clientX/Y

Given a screenX and screenY (coordinates on the screen), I want to convert to a clientX and clientY (coordinates on a document) in relation to a document/document.body in a given Window.

What I’m struggling with is how to account for the window chrome, titlebar, location bar, … as I can’t see any properties that can help.

Any ideas?

Google Translate Extention: how do i hide origin text in popups through code?

I am modifying this extention for myself. Already changed char limit and font size. But i can`t get rid of origin text. Whole day sitting over this problem, sanity running low.
Can anyone help pls?

That’s what i am trying to delete

Can`t post files here, so uploaded it on Github or u can just download it from Extention Store.

Tell me if u need some more information.
Thanks

Javascript get first and last date by month

I’m trying to get the first and last date of the month, but I get an unexpected result.

This is my code:

var year = 2024;
var month = 9; // October
var firstDay = new Date(year, month, 1);
firstDate = firstDay.toISOString().split('T')[0];
document.getElementById("a").innerHTML = firstDay+" | "+firstDate;

var lastDay = new Date(year, month+1, 0);
lastDate = lastDay.toISOString().split('T')[0];
document.getElementById("b").innerHTML = lastDay+" | "+lastDate;
<p id="a"></p>
<p id="b"></p>

I would like to have as result 2024-10-01 and 2024-10-31 as Date() days.

Is this a GMT issue? Should I add hours?

Thanks

Mismatched GoogleDataTransportCCTSupport version in Podfile with Expo 52.0.9 and React Native 0.76.2

I am experiencing an issue with GoogleDataTransportCCTSupport in the Podfile when integrating react-native-firebase in my iOS project. Despite upgrading and downgrading the library versions, the problem persists. Below are the relevant details of my project and the error I am encountering.

Here are the relevant details of my setup:

  • Expo Version: 52.0.9

  • React Native Version: 0.76.2

  • react-native-firebase Version: 14.12.0

  • Xcode Version: 16.1 (16B40)

  • iOS Version: 15.1

Problem:

After installing react-native-firebase and resolving all dependencies, I’m getting a build failure due to mismatched versions of GoogleDataTransportCCTSupport. The error message I’m encountering is:

csharp

Copy code

[ERROR] Mismatched version of GoogleDataTransportCCTSupport in the Podfile.

Steps I’ve Tried:

  1. Upgraded and downgraded react-native-firebase to different versions.

  2. Ran pod repo update and pod install after clearing the derived data.

  3. Manually modified the Podfile to try and resolve the version conflict, but nothing seems to work.

Podfile Snippet (Relevant Section):

ruby

Copy code

# Podfile platform :ios, '11.0' target 'MyApp' do use_frameworks! pod 'GoogleDataTransportCCTSupport', 'X.X.X' # Tried with different versions pod 'react-native-firebase', '~> 14.12.0' # Other dependencies end

Error Message:

bash

Copy code

[ERROR] Mismatched version of GoogleDataTransportCCTSupport in the Podfile.

Questions:

  1. What is the correct version of GoogleDataTransportCCTSupport for react-native-firebase version 14.12.0 with React Native 0.76.2 and Expo 52.0.9?

  2. How can I resolve the version mismatch in my Podfile?

  3. Are there any other dependencies I should check to avoid version conflicts with Firebase SDKs in a React Native project?

Additional Information:

  • I’ve tried removing the version constraints in the Podfile for GoogleDataTransportCCTSupport but the issue persists.

  • I’ve checked the Firebase and react-native-firebase documentation for compatibility, but I’m still facing this issue.

Disable Angular Animations as a User

We are automating a third party application using RPA. The application is written in Angular 17 and has many default animations enabled (like expanding a collapsed panel) – which lead to delays and possible problems.

The page is unaffected by ‘prefers-reduced-animation’ or the OS setting to disable animations.

I have also tried to inject a global style via JavaScript * { animations: none!important; transitions: none!important } which had no effect.

It seems Angular is setting the relevant properties directly in the .style attribute of the elements (including !important)

We cannot access/change the source code of the application, but we can execute custom JavaScript on the page. I ideally would like a robust solution which disables all transition/animations.

How do I get this canvas image to crop exactly?

I’m trying to create a webpage for use on a mobile phone. The idea is that you will take a picture of a haematocrit tube (see here then crop to the top and bottom of the filled portion of the tube, then mark the top and bottom of the buffy coat to calculate the haematocrit and the buffy coat size. Because the resultant picture will be zoomed in on most mobile phones I expect measuring the size of the buffy coat to be more accurate than using conventional techniques.

The page is currently showing a ruler rather than the camera for debugging purposes. My problem is that I need to be able to crop exactly to the area bordered by the green and blue bars for the next stage to be accurate and – well its not. I’ve tried an absolute and a relative correction to the sizes; unfortunately the degree of error seems to depend on where on the image the crop zone is so neither of these work well.

I have a hunch that I might have made this problem for myself by using vh as the initial height units, but the code does calculate the canvas height in pixels (using canvas.height returns the entire window height, and I don’t know why because the canvas definitely doesn’t seem to be extending underneath the prompt bar – and yet, if I don’t have z-index:10 on the prompt bar it gets hidden.

I am, in short, quite confused. So is Claude. Does anyone have any ideas on how to get the cropping exact please?

(NB: The bars are designed to be dragged via touch, so switch to mobile mode in DevTools to get it to work properly).

PHP page submission with SQL database

The project is an employee timesheet page that I’ve been working on. The users come and select the client that they were working with, the date and enter in the hours they used from dropdown menus.

I managed to give it some error handling to try and debug it but now I’m stuck. It gives me 2 errors when I submit the dummy data:

  1. No Project ID selected
  2. Invalid Section ID

The project ID is meant to come from another table, but it is not registering here for some reason.

new_sheet.php:

<?php
// Connect to the database
include('db_connect.php');

// Fetch current user role and ID from the session
$login_id = $conn->real_escape_string($_SESSION['login_id']);
$login_type = $_SESSION['login_type']; // Assuming 1 = Partner, 2 = Manager, 3 = Staff

// Get the project ID from the URL (if provided), or fallback to the selected project from the form
$project_id = isset($_GET['project_id']) ? (int)$_GET['project_id'] : 0;

// Check if the project_id is valid
if ($project_id > 0) {
    $project_check_query = $conn->query("SELECT id FROM project_list WHERE id = {$project_id}");
    if ($project_check_query->num_rows == 0) {
        die('Error: Invalid project ID. The selected project does not exist.');
    }
} else {
    // If no project ID in URL, display a message or a default project selection
    echo "<div class='alert alert-danger'>Error: No project ID selected.</div>";
}

// Fetch the client name based on the project ID
$client_name = "";
if ($project_id > 0) {
    $client_query = $conn->query(
        "SELECT c.client AS client_name 
         FROM project_list p 
         LEFT JOIN client_list c ON p.client_id = c.id 
         WHERE p.id = {$project_id}"
    );
    if ($client_query && $client_query->num_rows > 0) {
        $client_name = $client_query->fetch_assoc()['client_name'];
    } else {
        $client_name = "Client not found";
    }
} else {
    $client_name = "No project selected";
}

// Adjust SQL query based on user type
if ($login_type == 3) { // If user is Staff
    $sql = "SELECT p.*, c.client AS client_name
            FROM project_list p
            LEFT JOIN client_list c ON p.client_id = c.id
            INNER JOIN project_audit_members pam ON p.id = pam.project_id
            WHERE pam.user_id = {$login_id}
            ORDER BY p.name ASC";
} else { // If user is Partner or Manager
    $sql = "SELECT p.*, c.client AS client_name
            FROM project_list p
            LEFT JOIN client_list c ON p.client_id = c.id
            ORDER BY p.name ASC";
}

// Execute the query
$qry = $conn->query($sql);

// Check for query errors
if (!$qry) {
    die('Error: ' . $conn->error);
}

// Define custom titles for each table
$table_titles = [
    "1. Finalisation", 
    "2. Review", 
    "3. Drafting", 
    "4. Planning",
    "5. Statutory", 
    "6. Assets", 
    "7. Liabilities & Obligations", 
    "8. Turnover",
    "9. Expenses", 
    "10. Others"
];

// Define custom data for each section
$section_data = [
    ["1.1 LOR and LOW/ML", "1.2 Outstanding Issues/ Matters Carried Forward", "1.3 Correspondence & Faxes", "1.4 Checklists - ISA, IAS, Audit Completion, Co's Act"], 
    ["2.1 Partners' Review", "2.2 Senior/ Supervisor's Review", "2.3 Manager's Review"], 
    ["3.1 Drafting Final Accounts & Finalisation", "3.2 Tax Computation & Tax Notes", "3.3 Feeding Client TB in VT"], 
    ["4.1 Audit Planning", "4.2 Analytical Review", "4.3 Main Ratios"], 
    ["5.1 Statutory/ Secretarial Audit", "5.2 Directors', Partners'/ Proprietors' Acc", "5.3 Taxation Account", "5.4 Deferred Tax Computation", "5.5 VAT Acc & VAT Analysis"], 
    ["6.1 Property, Plant & Equipments", "6.2 Other Assets", "6.3 Inventory/Work In Progress", "6.4 Account Receivable & Prepayments", "6.5 Cash & Bank"], 
    ["7.1 Accounts Payable & Accruals", "7.2 Loans", "7.3 Hire Purchase Obligations", "7.4 Other Liabilities", "7.5 Bank Overdrafts"], 
    ["8.1 Sales Income/ Revenue", "8.2 Other Income - Interest, Rent, Etc."], 
    ["9.1 Purchase Test", "9.2 Other Cost of Sales", "9.3 Salaries & Wages", "9.4 All Other Expenses"], 
    ["10.1 PAF", "10.2 Understanding the Client", "10.3 Printing & Finalisation"]
];

// Process form submission
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $work_date = $_POST['work_date'];
    $section_id = $_POST['section_id'] ?? [];  // Default to empty array if section_id is not set
    $start_time = $_POST['start_time'] ?? [];
    $end_time = $_POST['end_time'] ?? [];
    $total_hours = $_POST['total_hours'] ?? [];

    // Ensure section_id is not empty
    if (empty($section_id)) {
        die("Error: No section selected.");
    }

    // Validate if section_id exists in the appropriate table (if needed)
    foreach ($section_id as $section) {
        $section_check_query = $conn->query("SELECT id FROM section_list WHERE title = '{$section}'");
        if (!$section_check_query || $section_check_query->num_rows == 0) {
            die("Error: Invalid section ID.");
        }
    }

    // Insert the timesheet data
    foreach ($section_id as $key => $section) {
        $start = $conn->real_escape_string($start_time[$key]);
        $end = $conn->real_escape_string($end_time[$key]);
        $total = $conn->real_escape_string($total_hours[$key]);

        $stmt = $conn->prepare(
            "INSERT INTO employee_timesheet 
            (user_id, project_id, work_date, section_id, start_time, end_time, total_hours) 
            VALUES (?, ?, ?, ?, ?, ?, ?)"
        );
        $stmt->bind_param(
            "iissssd", 
            $login_id, 
            $project_id, 
            $work_date, 
            $section, 
            $start, 
            $end, 
            $total
        );

        if (!$stmt->execute()) {
            die("Error inserting data: " . $stmt->error);
        }
    }

    echo "<div class='alert alert-success'>Timesheet successfully saved!</div>";
}
?>

<!-- HTML Section -->
<div class="row">
    <div class="col-md-6">
        <div class="card">
            <div class="card-body">
                <form method="GET">
                    <label for="client_id">Select Client:</label>
                    <select id="client_id" name="project_id" class="form-control" required>
                        <option value="">-- Select Client --</option>
                        <?php while ($row = $qry->fetch_assoc()) : ?>
                            <option value="<?php echo htmlspecialchars($row['id']); ?>" 
                                <?php echo ($row['id'] == $project_id) ? 'selected' : ''; ?> >
                                <?php echo htmlspecialchars($row['client_name']); ?>
                            </option>
                        <?php endwhile; ?>
                    </select>
                </form>
            </div>
        </div>
    </div>

    <div class="col-md-6">
        <div class="card">
            <div class="card-body">
                <form method="POST">
                    <label for="work_date">Work Date:</label>
                    <input type="date" name="work_date" id="work_date" class="form-control" required>
                </form>
            </div>
        </div>
    </div>
</div>

<hr>

<div class="row">
    <?php foreach ($table_titles as $tableIndex => $title) : ?>
        <div class="col-md-6">
            <div class="card card-outline card-success">
                <div class="card-header">
                    <b><?php echo htmlspecialchars($title); ?></b>
                    <button class="btn btn-link btn-sm" type="button" data-toggle="collapse" data-target="#section-<?php echo $tableIndex; ?>" aria-expanded="false">
                        <i class="fas fa-chevron-down"></i>
                    </button>
                </div>
                <div id="section-<?php echo $tableIndex; ?>" class="collapse">
                    <div class="card-body">
                        <form method="POST">
                            <input type="hidden" name="project_id" value="<?php echo $project_id; ?>">
                            <?php foreach ($section_data[$tableIndex] as $section) : ?>
                                <input type="hidden" name="section_id[]" value="<?php echo htmlspecialchars($section); ?>">
                            <?php endforeach; ?>
                            <input type="date" name="work_date" value="<?php echo date('Y-m-d'); ?>" hidden>
                            <table class="table">
                                <thead>
                                    <tr>
                                        <th>Task</th>
                                        <th>Start Time</th>
                                        <th>End Time</th>
                                        <th>Total Hours</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <?php foreach ($section_data[$tableIndex] as $index => $task) : ?>
                                        <tr>
                                            <td><?php echo htmlspecialchars($task); ?></td>
                                            <td>
                                                <input type="time" name="start_time[]" class="form-control start-time">
                                            </td>
                                            <td>
                                                <input type="time" name="end_time[]" class="form-control end-time">
                                            </td>
                                            <td>
                                                <input type="text" name="total_hours[]" class="form-control total-hours" readonly>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                </tbody>
                            </table>
                            <button type="submit" class="btn btn-success mt-2">Submit Timesheet</button>
                        </form>
                    </div>
                </div>
            </div>
        </div>

        <?php if (($tableIndex + 1) % 2 == 0) : ?>
            </div><div class="row">
        <?php endif; ?>
    <?php endforeach; ?>
</div>

<script>
    // Function to calculate time difference and auto-fill Total Hours
    document.querySelectorAll('input[type="time"]').forEach(input => {
        input.addEventListener('input', () => {
            const row = input.closest('tr');
            const startTime = row.querySelector('.start-time').value;
            const endTime = row.querySelector('.end-time').value;

            if (startTime && endTime) {
                const start = new Date(`1970-01-01T${startTime}Z`);
                const end = new Date(`1970-01-01T${endTime}Z`);
                const diff = (end - start) / (1000 * 60 * 60);

                const totalHours = diff > 0 ? diff.toFixed(2) : "0.00";
                row.querySelector('.total-hours').value = totalHours;
            }
        });
    });
</script>

employee_sheet table schema:

id int(11) NO PRI NULL auto_increment
user_id int(11) NO MUL NULL
project_id int(11) NO MUL NULL
work_date date NO NULL
section_id int(11) NO MUL NULL
start_time time NO NULL
end_time time NO NULL
total_hours decimal(5,2) NO NULL

project_list table schema:

id int(11) NO PRI NULL auto_increment
name varchar(200) NO NULL
description text NO NULL
status tinyint(2) NO NULL
start_date date NO NULL
end_date date NO NULL
manager_id int(30) NO NULL
user_ids text NO NULL
date_created datetime NO current_timestamp()
client_id int(11) NO MUL NULL

I was expecting it to submit onto the database table in the appropriate fields, but it isn’t.

Parse JSON to get only parent name, and not its object [duplicate]

I have looked at JSON parsing to get parent name which shows how to get a single parent name (without going into its child object).
But how would I get sibling parents, such that i need an array of someParentName, someParentName3 ?

const obj = { 
 "someParentName":{
   "somechild":{
     "value1":"test"
   }
 },
  "someParentName3":{
   "somechild":{
     "value1":"test3"
   }
 }
}

My actual data is as follows, where what i need is the names of the participants: contact5 and contact6.

{
   "id":"1733153805736",
   "participants":{
      "contact5":{
         "access":"ReadWrite",
         "notify":true,
         "isUnread":true,
         "joinedAt":1733153806310
      },
      "contact6":{
         "access":"ReadWrite",
         "notify":true,
         "isUnread":true,
         "joinedAt":1733153806314
      }
   },
   "custom":{
      
   },
   "subject":"more cats",
   "createdAt":1733153806307,
   "topicId":null,
   "photoUrl":null,
   "welcomeMessages":[
      "Regarding: Let's continue discussing this here"
   ],
   "lastMessage":{
      "id":"msg_4t2rWzoYH2YgsTk6bXqvWn",
      "type":"UserMessage",
      "origin":"rest",
      "location":null,
      "text":"Let's continue discussing this here",
      "content":[
         {
            "type":"text",
            "children":[
               "Let's continue discussing this here"
            ]
         }
      ],
      "attachment":null,
      "custom":{
         "forwardedFrom":"1733236324331"
      }
   }
}