SharePoint Classic: Daily Work Log Web Part Tutorial
Hey guys! Today, we're diving into classic SharePoint to build a fantastic daily work log and team directory web part. This is perfect if you're still rocking the classic experience and want a simple, effective way to track your work and keep your team connected. No need for Power Apps here β we're going old-school with a Web Part Page and a Script Editor. Let's get started!
Why This Web Part?
Before we jump into the how-to, let's talk about why this is useful. This web part will allow you to:
- Save your daily work entries directly to a SharePoint list.
- Display your recent work entries for easy reference.
- Show your team details pulled from another SharePoint list.
I'll walk you through the whole process, providing:
- Exact list setup instructions.
- An optional file/folder map for organization.
- A ready-to-paste code block that makes it all work.
1) Setting Up Your SharePoint Lists
First things first, we need to create the SharePoint lists that will store our data. We'll need two lists:
A) DailyWorkLog (For Your Work Entries)
This list will hold your daily work log entries. Here's how to set it up:
- List name: DailyWorkLog
- Columns:
- Title β Single line of text (Use this for "Work summary")
- WorkDate β Date and Time (Choose "Date only")
- Hours β Number (Allow 2 decimals)
- Category β Choice (e.g., "Analysis;Build;Testing;Meetings;Support"; select "Drop-Down Menu")
- Notes β Multiple lines of text (Plain text)
 
Pro Tip: Keep the list name exactly
DailyWorkLog(without spaces). This ensures the internal type is predictable, and the script will work seamlessly. Trust me, this little detail saves headaches down the road!
Setting up the DailyWorkLog list is crucial for tracking your daily activities efficiently. Think of the Title column as a short summary of what you accomplished, like "Built a new feature" or "Fixed a critical bug". The WorkDate column, obviously, logs the date you performed the work. The Hours column is where you'll input the time spent on the task, allowing for precise tracking with two decimal places, like 2.75 hours. Category is a super helpful way to classify your work, making it easier to analyze where your time is going β are you spending most of your time in Analysis, Build, or Support? The Notes section is your free space to add any additional details or context about the work you did, giving you a comprehensive record of your day.
When configuring the Category column, make sure to choose "Drop-Down Menu" as the display choice. This will provide a clean and user-friendly way to select a category from the list you provide, preventing any inconsistencies in the data. The categories I suggested β Analysis, Build, Testing, Meetings, and Support β are fairly common in many workplaces, but you can customize these to perfectly fit the types of work you do. The flexibility to add or remove categories later is a great advantage.
The best practice of keeping the list name as DailyWorkLog is not just a suggestion, it's a critical step to ensure that the JavaScript code we'll be using later works without any modifications. SharePoint uses internal names for lists and columns, and if you deviate from this exact naming convention, the script won't be able to find the list. This will save you the trouble of having to dive into the code and update the list references, which can be a bit daunting if you're not super familiar with JavaScript.
B) TeamMembers (For Listing Your Team)
This list will hold the details of your team members. Here's the setup:
- List name: TeamMembers
- Columns:
- Title β Single line of text (Use for Member Name)
- Role β Single line of text (or Choice if you prefer)
- Email β Single line of text
- Phone β Single line of text (or Number)
- (Optional) Manager β Yes/No
 
Populate a few rows with your team member details so the web part has something to display. This makes testing easier and gives you a visual confirmation that everything is working as expected.
The TeamMembers list is a simple yet effective way to create a directory within SharePoint. The Title column will store the names of your team members, serving as the primary identifier. Role is great for specifying each member's position or responsibilities, which can be especially useful in larger teams. You have the flexibility to make this a simple text field or a Choice field, depending on whether you want to standardize the roles. Email and Phone are essential for contact information, making it easy to get in touch with team members directly from the web part. The optional Manager column (Yes/No) can be a quick way to filter the team list by managerial roles, which can be useful for various reporting or organizational purposes.
Populating the TeamMembers list with a few sample entries is a smart move before you proceed further. This will allow you to see the web part in action right away, displaying the information you've entered. It's a quick way to verify that the data is being pulled correctly and displayed in the desired format. Plus, it's much more satisfying to see a functional web part with actual data rather than a blank slate. It also helps to identify any potential issues with data types or formatting early on, saving you time and frustration later.
Permissions (A Quick Default Setup)
To keep things simple, here's a basic permission setup:
- DailyWorkLog: Members = Contribute; Visitors = Read
- TeamMembers: Everyone who should see the team = Read; only you/admins = Contribute
This ensures that team members can add their own work logs, and everyone can view the team directory. Setting the right permissions is critical for maintaining the security and integrity of your data. For the DailyWorkLog list, allowing Members to Contribute means they can add, edit, and delete their own entries, while Visitors can only view the entries. This is a common setup for a personal work log where each member is responsible for their own data.
For the TeamMembers list, the permissions are slightly different. Giving Read access to everyone who needs to see the team directory ensures that the information is accessible to the right people. Restricting Contribute access to only you and administrators maintains the integrity of the team data, preventing unauthorized modifications. This is particularly important for contact information and roles, which should be accurate and up-to-date.
2) Creating the Web Part Page
Now that our lists are ready, let's create the page where we'll display our web part:
- Go to Site Pages (or Pages) β New β Web Part Page.
- Name: DailyWorkHub.aspx
- Layout: "Full Page, Vertical" (or any layout you prefer)
- Open the page β Edit β add a Script Editor (or Content Editor) Web Part.
- Click Edit Snippet and paste the code below β Insert β Save (Check in/Publish if required).
Creating a Web Part Page is the next step in bringing your daily work log and team directory to life. Naming the page DailyWorkHub.aspx makes it clear what the page is for and follows a consistent naming convention for SharePoint pages. Choosing a layout like "Full Page, Vertical" gives you a clean and organized structure to work with, but the beauty of SharePoint is that you can select any layout that suits your needs. The most important thing is to have a layout that provides enough space for your web part and any other content you might want to add in the future.
Adding a Script Editor (or Content Editor) Web Part is where the magic happens. This web part allows you to embed custom code, like HTML, CSS, and JavaScript, directly into your SharePoint page. This is how we'll be injecting the functionality of our daily work log and team directory. The Script Editor Web Part is a classic way to add custom functionality to SharePoint pages, and it's perfect for this project.
Once you've added the Script Editor Web Part, clicking Edit Snippet opens up a text box where you can paste the code that I'll provide in the next step. After pasting the code, click Insert to embed it into the web part, and then Save the page to see your web part in action. If your SharePoint environment requires it, you might also need to Check in and Publish the page to make it visible to others.
3) Paste-and-Run Code (All-In-One)
Okay, the moment you've been waiting for! Here's the code that will power our web part:
β This works in classic SharePoint using JSOM (no external libraries needed!). It writes to
DailyWorkLogand reads from both lists. Make sure you've created the lists exactly as described above for this code to work without modifications.
<div id="worklog-app" style="font-family:Segoe UI, Arial, sans-serif; max-width:1100px; margin:24px auto;">
 <h1 style="margin:0 0 12px;">Daily Work Hub</h1>
 <p style="margin:0 0 24px;color:#444;">Log todayβs work and see your team at a glance.</p>
 <!-- Form -->
 <div class="card" style="padding:16px;border:1px solid #e1e1e1;border-radius:12px;margin-bottom:16px;">
 <h2 style="margin:0 0 12px;font-size:18px;">Add Work Entry</h2>
 <div style="display:grid;grid-template-columns:repeat(2,minmax(260px,1fr));gap:12px;">
 <label>Work Summary (Title)
 <input id="wl-title" type="text" style="width:100%;padding:8px;border:1px solid #ccc;border-radius:8px;" placeholder="e.g., Built list view & fixed column order">
 </label>
 <label>Date
 <input id="wl-date" type="date" style="width:100%;padding:8px;border:1px solid #ccc;border-radius:8px;">
 </label>
 <label>Hours
 <input id="wl-hours" type="number" step="0.25" min="0" style="width:100%;padding:8px;border:1px solid #ccc;border-radius:8px;" placeholder="e.g., 2.5">
 </label>
 <label>Category
 <select id="wl-category" style="width:100%;padding:8px;border:1px solid #ccc;border-radius:8px;">
 <option value="">-- Select --</option>
 <option>Analysis</option>
 <option>Build</option>
 <option>Testing</option>
 <option>Meetings</option>
 <option>Support</option>
 </select>
 </label>
 <label style="grid-column:1/-1;">Notes
 <textarea id="wl-notes" rows="3" style="width:100%;padding:8px;border:1px solid #ccc;border-radius:8px;" placeholder="Optional detailsβ¦"></textarea>
 </label>
 </div>
 <div style="margin-top:12px;display:flex;gap:8px;align-items:center;">
 <button id="wl-save" style="padding:10px 16px;border:0;border-radius:8px;background:#004578;color:#fff;cursor:pointer;">Save Entry</button>
 <span id="wl-status" style="color:#666;"></span>
 </div>
 </div>
 <!-- Recent entries -->
 <div class="card" style="padding:16px;border:1px solid #e1e1e1;border-radius:12px;margin-bottom:16px;">
 <h2 style="margin:0 0 12px;font-size:18px;">My Recent Entries</h2>
 <div id="wl-my-entries" style="overflow:auto;border:1px solid #f0f0f0;border-radius:8px;"></div>
 </div>
 <!-- Team -->
 <div class="card" style="padding:16px;border:1px solid #e1e1e1;border-radius:12px;">
 <h2 style="margin:0 0 12px;font-size:18px;">Team Directory</h2>
 <div id="wl-team" style="overflow:auto;border:1px solid #f0f0f0;border-radius:8px;"></div>
 </div>
</div>
<script type="text/javascript">
(function () {
 // Ensure SharePoint JSOM is available
 function ready(fn){ if(document.readyState!=='loading'){ fn(); } else { document.addEventListener('DOMContentLoaded', fn); } }
 ready(function(){
 if (window.SP && SP.SOD && SP.SOD.executeFunc) {
 SP.SOD.executeFunc('sp.js', 'SP.ClientContext', initApp);
 } else {
 // Fallback if SOD not present
 var s = document.createElement('script');
 s.src = '/_layouts/15/sp.js';
 s.onload = initApp;
 document.head.appendChild(s);
 }
 });
 function initApp(){
 // Pre-fill today
 var d = new Date();
 document.getElementById('wl-date').value = toYYYYMMDD(d);
 document.getElementById('wl-save').addEventListener('click', saveEntry);
 loadMyEntries();
 loadTeam();
 }
 function context(){ return new SP.ClientContext.get_current(); }
 function toYYYYMMDD(dt){
 var y = dt.getFullYear();
 var m = String(dt.getMonth()+1).padStart(2,'0');
 var d = String(dt.getDate()).padStart(2,'0');
 return y + '-' + m + '-' + d;
 }
 function setStatus(msg, isError){
 var el = document.getElementById('wl-status');
 el.textContent = msg || '';
 el.style.color = isError ? '#a80000' : '#666';
 }
 function validate(){
 var title = document.getElementById('wl-title').value.trim();
 var date = document.getElementById('wl-date').value;
 var hours = document.getElementById('wl-hours').value;
 var cat = document.getElementById('wl-category').value;
 if(!title){ return 'Please enter Work Summary.'; }
 if(!date){ return 'Please select Date.'; }
 if(hours==='' || isNaN(parseFloat(hours)) || parseFloat(hours) < 0){ return 'Please enter valid Hours.'; }
 if(!cat){ return 'Please choose a Category.'; }
 return '';
 }
 function saveEntry(){
 setStatus('Savingβ¦');
 var err = validate();
 if(err){ setStatus(err, true); return; }
 var ctx = context();
 var web = ctx.get_web();
 var list = web.get_lists().getByTitle('DailyWorkLog');
 var itemCreateInfo = new SP.ListItemCreationInformation();
 var item = list.addItem(itemCreateInfo);
 item.set_item('Title', document.getElementById('wl-title').value.trim());
 // Date fields need a JS Date
 var dt = new Date(document.getElementById('wl-date').value + 'T00:00:00');
 item.set_item('WorkDate', dt);
 item.set_item('Hours', parseFloat(document.getElementById('wl-hours').value));
 item.set_item('Category', document.getElementById('wl-category').value);
 item.set_item('Notes', document.getElementById('wl-notes').value.trim());
 item.update();
 ctx.executeQueryAsync(function(){
 setStatus('Saved β
');
 // Clear quick
 document.getElementById('wl-title').value='';
 document.getElementById('wl-hours').value='';
 document.getElementById('wl-category').value='';
 document.getElementById('wl-notes').value='';
 loadMyEntries();
 }, function(sender,args){
 setStatus('Error: ' + args.get_message(), true);
 });
 }
 function loadMyEntries(){
 var ctx = context();
 var web = ctx.get_web();
 var list = web.get_lists().getByTitle('DailyWorkLog');
 var caml = new SP.CamlQuery();
 // Filter to current user and order by WorkDate desc, then Created desc; top 20
 caml.set_viewXml(
 "<View>" +
 "<Query>" +
 "<Where>" +
 "<Eq>" +
 "<FieldRef Name='Author' LookupId='TRUE'/>" +
 "<Value Type='Integer'><UserID/></Value>" +
 "</Eq>" +
 "</Where>" +
 "<OrderBy><FieldRef Name='WorkDate' Ascending='FALSE'/><FieldRef Name='Created' Ascending='FALSE'/></OrderBy>" +
 "</Query>" +
 "<RowLimit>20</RowLimit>" +
 "</View>"
 );
 var items = list.getItems(caml);
 ctx.load(items);
 ctx.executeQueryAsync(function(){
 var html = [];
 html.push("<table style='width:100%;border-collapse:collapse;font-size:14px;'>");
 html.push("<thead><tr style='background:#f9f9f9;'>"+
 th('Work Date')+th('Title')+th('Category')+th('Hours')+th('Notes')+
 "</tr></thead><tbody>");
 var enumerator = items.getEnumerator();
 var hasAny = false;
 while(enumerator.moveNext()){
 hasAny = true;
 var it = enumerator.get_current();
 html.push("<tr style='border-top:1px solid #eee;'>"+
 td(formatDate(it.get_item('WorkDate')))+
 td(esc(it.get_item('Title')))+
 td(esc(it.get_item('Category')||''))+
 td(String(it.get_item('Hours')||''))+
 td(esc(it.get_item('Notes')||''))+
 "</tr>");
 }
 if(!hasAny){
 html.push("<tr><td colspan='5' style='padding:12px;color:#666;'>No entries yet. Add your first one above.</td></tr>");
 }
 html.push("</tbody></table>");
 document.getElementById('wl-my-entries').innerHTML = html.join('');
 }, function(sender,args){
 document.getElementById('wl-my-entries').innerHTML =
 "<div style='padding:12px;color:#a80000;'>Error loading entries: "+ esc(args.get_message()) +"</div>";
 });
 }
 function loadTeam(){
 var ctx = context();
 var web = ctx.get_web();
 var list = web.get_lists().getByTitle('TeamMembers');
 var caml = new SP.CamlQuery();
 caml.set_viewXml(
 "<View>" +
 "<Query>" +
 "<OrderBy><FieldRef Name='Title' Ascending='TRUE'/></OrderBy>" +
 "</Query>" +
 "<RowLimit>200</RowLimit>" +
 "</View>"
 );
 var items = list.getItems(caml);
 ctx.load(items);
 ctx.executeQueryAsync(function(){
 var html = [];
 html.push("<table style='width:100%;border-collapse:collapse;font-size:14px;'>");
 html.push("<thead><tr style='background:#f9f9f9;'>"+
 th('Name')+th('Role')+th('Email')+th('Phone')+
 "</tr></thead><tbody>");
 var enumerator = items.getEnumerator();
 var hasAny = false;
 while(enumerator.moveNext()){
 hasAny = true;
 var it = enumerator.get_current();
 html.push("<tr style='border-top:1px solid #eee;'>"+
 td(esc(it.get_item('Title')||''))+
 td(esc(it.get_item('Role')||''))+
 td(renderEmail(it.get_item('Email')||''))+
 td(esc(it.get_item('Phone')||''))+
 "</tr>");
 }
 if(!hasAny){
 html.push("<tr><td colspan='4' style='padding:12px;color:#666;'>No team members found.</td></tr>");
 }
 html.push("</tbody></table>");
 document.getElementById('wl-team').innerHTML = html.join('');
 }, function(sender,args){
 document.getElementById('wl-team').innerHTML =
 "<div style='padding:12px;color:#a80000;'>Error loading team: "+ esc(args.get_message()) +"</div>";
 });
 }
 // Helpers
 function th(s){ return "<th style='text-align:left;padding:10px;border-bottom:1px solid #eee;'>" + s + "</th>"; }
 function td(s){ return "<td style='padding:10px;vertical-align:top;'>" + s + "</td>"; }
 function esc(s){ return String(s).replace(/[&<>\"]/g, function(c){ return ({'&':'&','<':'<','>':'>','"':'"'}[c]); }); }
 function renderEmail(e){ if(!e) return ''; var x = esc(e); return "<a href='mailto:"+ x +"'>"+ x +"</a>"; }
 function formatDate(d){
 if(!d) return '';
 try {
 var dt = new Date(d);
 if(isNaN(dt)) return esc(d);
 return dt.getFullYear() + '-' + String(dt.getMonth()+1).padStart(2,'0') + '-' + String(dt.getDate()).padStart(2,'0');
 } catch(e){ return esc(d); }
 }
})();
</script>
Just paste this entire block of code into the Script Editor Web Part, hit Insert, and then Save the page. Boom! You should see your Daily Work Hub in action.
This code block is the heart and soul of our web part. It's a self-contained unit of HTML, CSS, and JavaScript that works together to create the user interface and functionality we need. The HTML structures the layout of the web part, including the form for adding work entries, the display of recent entries, and the team directory. The CSS styles the elements, making them visually appealing and consistent with the SharePoint environment. The JavaScript is the brains of the operation, handling user interactions, data validation, saving entries to the DailyWorkLog list, and retrieving and displaying data from both lists.
The code is designed to work in classic SharePoint using JSOM (JavaScript Object Model), which is SharePoint's client-side API for interacting with SharePoint data. This means we don't need to rely on any external libraries, keeping the solution lightweight and easy to deploy. The code first checks to ensure that SharePoint's JSOM library is available, and if not, it loads it dynamically. This ensures that the code will work even in environments where JSOM might not be readily accessible.
The initApp function is the entry point for our web part. It pre-fills the date field with today's date, attaches an event listener to the "Save Entry" button, and calls the loadMyEntries and loadTeam functions to retrieve and display the recent work entries and team directory, respectively. These functions use JSOM to query the SharePoint lists, format the data, and inject it into the appropriate sections of the web part.
One of the key functions in the code is saveEntry, which handles the submission of new work log entries. It first validates the user input to ensure that all required fields are filled in correctly. If there are any validation errors, it displays an error message to the user. If the input is valid, it uses JSOM to create a new item in the DailyWorkLog list, setting the values of the various columns based on the user's input. After the item is saved successfully, it clears the form fields and refreshes the recent entries list to reflect the new entry.