ChrisF323949498
Tera Explorer

Hello,

So in part 1 – we got a basic reactJS working in UI page via the IDE.

I played around a bit more with the IDE and created this:

ChrisF323949498_0-1757948827927.png

 

It gives a summary of incidents, changes, problems in some tiles – A mini dashboard!
Nothing ground breaking I know, but it combines TypeScript + ReactJS + CSS all via the IDE in ServiceNow and gets us data from the platform, without any complexities around authentication etc. A good starting point. 

 

For this, I wrote the following code (just change the App.jsx file, from what we had in Part 1).

 

I've added comments to the code below to explain some bits. 

 

import React, { useState, useEffect } from 'react';



// TypeScript interfaces - We want to get g_ck and keep typescript happy.

//You can learn typescript here: https://www.typescriptlang.org/docs/

//Honestly, it's great, but the errors youll encounter be frustrating... keep going though!

//

declare global {

  interface Window {

    g_ck: string;

  }

}



// Reusable Tile Component - More Typescript stuff here. Keeps it happy.

interface TileProps {

  title: string;

  tableName: string;

  listUrl: string;

}



const RecordTile: React.FC<TileProps> = ({ title, tableName, listUrl }) => {

  //The stuff after RecordTile: is more typescript.

  //JS would just be:

// const RecordTile = ({ title, tableName, listUrl }) => {

  const [count, setCount] = useState<number | null>(null); //ReactJS State - Updates to state, cause ReactJS to 'rerender' the page so the visual updates occur.

  const [loading, setLoading] = useState(true);



  //useEffect - This function will run on mount (when the page loads), but also when 'tableName' changes in the future.

  //For now, just think of is as 'On load... Do this...

  useEffect(() => {

    async function fetchCount() {

      try {

        setLoading(true);

        const result = await getRecordCount(tableName);

        setCount(result);

        setLoading(false);

      } catch (error) {

        console.error(`Failed to fetch ${tableName} count:`, error);

        setLoading(false);

      }

    }

    fetchCount();

  }, [tableName]);



  //React returns HTML (well, 'JSX') - It's like JS and HTML had a baby! ReactJS will render this for us.

  return (

    <div style={{

      border: '1px solid #ccc',

      borderRadius: '8px',

      padding: '16px',

      margin: '8px',

      boxShadow: '0 2px 4px rgba(0,0,0,0.1)',

      textAlign: 'center'

    }}>

      <h2 style={{ fontSize: '20px', fontWeight: 'bold', marginBottom: '8px' }}>{title}</h2>

      {loading ? (

        <p>Loading...</p>

      ) : count !== null ? (

        <a

          href={listUrl}

          style={{

            fontSize: '24px',

            fontWeight: 'bold',

            color: '#007bff',

            textDecoration: 'none'

          }}

          onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}

          onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}

        >

          {count}

        </a>

      ) : (

        <p>Error loading count</p>

      )}

    </div>

  );

};



// Main App Component - This is our 'main' function. the main.tsx file references this function in the <App> tag. Causing this function to run, and the stuff it returns

//goes where <App> is.

export default function App() {



  const handleButtonClick = () => {

    console.log('Button clicked! ReactJS in ServiceNow is working!');

  };



  return (

    <>

      <style>{`

        .container {

          padding: 16px;

        }

        .header {

          font-size: 24px;

          font-weight: bold;

          margin-bottom: 16px;

        }

        .grid {

          display: grid;

          grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));

          gap: 16px;

        }

        .button {

          margin-top: 16px;

          background-color: #007bff;

          color: white;

          font-weight: bold;

          padding: 8px 16px;

          border: none;

          border-radius: 4px;

          cursor: pointer;

        }

        .button:hover {

          background-color: #0056b3;

        }

      `}</style>

      <div className="container">

        <h1 className="header">ServiceNow Dashboard - ReactJS</h1>

        <div className="grid">

          {/* The below are function calls to the RecordTile function - We pass in parameters, by using the HTML attributes.

          E.g. tableName="incident" is the TableName parameter used above in the RecordTile function. Try adding your own, e.g. Incident tasks perhaps.

          */}

          <RecordTile title="Incidents" tableName="incident" listUrl="/incident_list.do" />

          <RecordTile title="Problems" tableName="problem" listUrl="/problem_list.do" />

          <RecordTile title="Changes" tableName="change_request" listUrl="/change_request_list.do" />

        </div>

        {/* Below is a HTML Button, but the onClick is pointing to a react function we declared above: handleButtonClick

       

        We put it into curlys {} to go into 'Javascript land' and call a function etc */}

        <button

          className="button"

          onClick={handleButtonClick}

        >

          Refresh Dashboard

        </button>

      </div>

    </>

  );

}



// Fetch function for record count

async function getRecordCount(tableName: string): Promise<number> {

  console.log(`Fetching ${tableName} count...`);



  try {

    const searchParams = new URLSearchParams();

    searchParams.set('sysparm_count', 'true');



    const response = await fetch(`/api/now/stats/${tableName}?${searchParams.toString()}`, {

      method: 'GET',

      headers: {

        Accept: 'application/json',

        'X-UserToken': window.g_ck,

      },

    });



    if (!response.ok) {

      const errorData = await response.json();

      console.error(`Error fetching ${tableName} count:`, errorData?.message);

      return 0;

    }



    const { result } = await response.json();

    return result.stats.count || 0;

  } catch (error) {

    console.error(`Error fetching ${tableName} count:`, error);

    return 0;

  }

}

 

 

I’ve used inline CSS, as Fluent does not yet support CSS modules as mentioned here

https://www.servicenow.com/docs/bundle/zurich-application-development/page/build/servicenow-sdk/conc...

 

 

In the next post, we’ll expand upon this and get the refresh button working.

 

I’ll also dive into seeing how best we can use CSS in the IDE as having it mixed with our HTML isn’t ideal.

 

I hope this helps give some guidance though, a little boilerplate perhaps!

Cheers all 😊